概述
在JDK的并发包里提供了几个非常有用的并发工具类。CountDownLatch、CyclicBarrier和Semaphore工具类提供了一种并发流程控制的手段,Exchanger工具类则提供了在线程间交换数据的一种手段。
1等待多线程完成的CountDownLatch
CoundDownLatch用法比较简单,和Thead.join()方法功能类似,但是更灵活,直接看示例
public class CountDownLatchTest {
staticCountDownLatch c = new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(1);
c.countDown();
System.out.println(2);
c.countDown();
}
}).start();
c.await();
System.out.println("3");
}
}
CountDownLatch不可能重新初始化或者修改CountDownLatch对象的内部计数
器的值。一个线程调用countDown方法happen-before,另外一个线程调用await方法。
2同步屏障CyclicBarrier
CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。
代码示例
public class CyclicBarrierTest {
staticCyclicBarrier c = new CyclicBarrier(2);
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
try {
c.await();
} catch (Exception e) {
}
System.out.println(1);
}
}).start();
try {
c.await();
} catch (Exception e) {
}
System.out.println(2);
}
}
CyclicBarrier还提供一个更高级的构造函数CyclicBarrier(int parties,Runnable barrier-Action),用于在线程到达屏障时,优先执行barrierAction(最后一个到达barrier的线程执行),方便处理更复杂的业务场景,如代码所示。
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierTest2 {
static CyclicBarrier c = new CyclicBarrier(2, new A());
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
try {
c.await();
} catch (Exception e) {
}
System.out.println(1);
}
}).start();
try {
c.await();
} catch (Exception e) {
}
System.out.println(2);
}
static class A implements Runnable {
@Override
public void run() {
System.out.println(3);
}
}
}
CyclicBarrier的应用举例,这里利用构造函数的第二个参数在最后一个到达屏障的线程执行的特性 计算汇总数据
import java.util.Map.Entry;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* 银行流水处理服务类
*
* @authorftf
*
*/
publicclass BankWaterService implements Runnable {
/**
* 创建4个屏障,处理完之后执行当前类的run方法
*/
private CyclicBarrier c = new CyclicBarrier(4, this);
/**
* 假设只有4个sheet,所以只启动4个线程
*/
private Executor executor = Executors.newFixedThreadPool(4);
/**
* 保存每个sheet计算出的银流结果
*/
private ConcurrentHashMap<String, Integer>sheetBankWaterCount = new ConcurrentHashMap<String, Integer>();
privatevoid count() {
for (inti = 0; i< 4; i++) {
executor.execute(new Runnable() {
@Override
publicvoid run() {
// 计算当前sheet的银流数据,计算代码省略
sheetBankWaterCount.put(Thread.currentThread().getName(), 1);
// 银流计算完成,插入一个屏障
try {
c.await();
} catch (InterruptedException |BrokenBarrierException e) {
e.printStackTrace();
}
}
});
}
}
@Override
publicvoid run() {
intresult = 0;
// 汇总每个sheet计算出的结果
for (Entry<String, Integer>sheet :heetBankWaterCount.entrySet()) {
result += sheet.getValue();
}
// 将结果输出
sheetBankWaterCount.put("result", result);
System.out.println(result);
}
publicstaticvoid main(String[] args) {
BankWaterService bankWaterCount = new BankWaterService();
bankWaterCount.count();
}
}
CyclicBarrier和CountDownLatch的区别
CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以
- 使用reset()方法重置
- getNumberWaiting方法可以获得Cyclic-Barrier阻塞的线程数量
- isBroken()方法用来了解阻塞的线程是否被中断。
所以CyclicBarrier能处理更为复杂的业务场景。
3控制并发线程数的Semaphore
Semaphore(信号量)是用来控制同时访问特定资源的线程数量,主要用于限流
代码示例
public class SemaphoreTest {
private static final int THREAD_COUNT = 30;
private static ExecutorServicethreadPool = Executors.newFixedThreadPool(THREAD_COUNT);
private static Semaphore s = new Semaphore(10);
public static void main(String[] args) {
for (inti = 0; i< THREAD_COUNT; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
s.acquire();
System.out.println("save data");
s.release();
} catch (InterruptedException e) {
}
}
});
}
threadPool.shutdown();
}
}
在代码中,虽然有30个线程在执行,但是只允许10个并发执行。Semaphore的构造方法Semaphore(int permits)接受一个整型的数字,表示可用的许可证数量
Semaphore还提供一些其他方法,具体如下。
- ·intavailablePermits():返回此信号量中当前可用的许可证数。
- ·intgetQueueLength():返回正在等待获取许可证的线程数。
- ·booleanhasQueuedThreads():是否有线程正在等待获取许可证。
- ·void reducePermits(int reduction):减少reduction个许可证,是个protected方法。
- ·Collection getQueuedThreads():返回所有等待获取许可证的线程集合,是个protected方法。
4线程间交换数据的Exchanger
Exchanger用于进行线程间的数据交换。它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。
Exchanger的应用场景
- 用于遗传算法
- 数据校对
下面代码示例用于数据校对.如果两个线程有一个没有执行exchange()方法,则会一直等待,如果担心有特殊情况发生,避免一直等待,可以使用exchange(V x,longtimeout,TimeUnit unit)设置最大等待时长。
public class ExchangerTest {
private static final Exchanger<String>exgr = new Exchanger<String>();
private static ExecutorServicethreadPool = Executors.newFixedThreadPool(2);
public static void main(String[] args) {
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
String A = "银行流水A"; // A录入银行流水数据
exgr.exchange(A);
} catch (InterruptedException e) {
}
}
});
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
String B = "银行流水B"; // B录入银行流水数据
String A = exgr.exchange("B");
System.out.println("A和B数据是否一致:" + A.equals(B) + ",A录入的是:"
+ A + ",B录入是:" + B);
} catch (InterruptedException e) {
}
}
});
threadPool.shutdown();
}
}