文章目录
本文介绍常用的 JUC 并发工具类,分别是CountDownLatch:等待多线程完成、Semaphore:控制并发线程数、CyclicBarrier:同步屏障、Exchanger: 交换者
一. CountDownLatch:等待多线程完成
CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
1.1 CountDownLatch使用示例
两个主要的方法: c.countDown()、 c.await();
public class CountDownLatchTest {
//构造器中的计数值(count)实际上就是闭锁需要等待的线程数量。
//这个值只能被设置一次,而且CountDownLatch没有提供任何机制去重新设置这个计数值。
static CountDownLatch c = new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(1);
//每调用一次这个方法,在构造函数中初始化的count值就减1
c.countDown();
System.out.println(2);
c.countDown();
}
}).start();
//主线程必须在启动其他线程后立即调用CountDownLatch.await()方法
c.await();
System.out.println("3");
}
}
1.2 CountDownLatch源码分析
这篇分析CountDownLatch的实现原理 已经分析的很透彻了,这里就不讲解了。
参考:
二. Semaphore:控制并发线程数
Semaphore用于保证同一时间并发访问线程的数目。信号量在操作系统中是很重要的概念,Java并发库里的Semaphore就可以很轻松的完成类似操作系统信号量的控制。Semaphore可以很容易控制系统中某个资源被同时访问的线程个数。
以下部分内容引自 《java并发编程的艺术》第8章
Semaphore可以用于做流量控制,特别是公用资源有限的应用场景,比如数据库连接。假
如有一个需求,要读取几万个文件的数据,因为都是IO密集型任务,我们可以启动几十个线程并发地读取,但是如果读到内存后,还需要存储到数据库中,而数据库的连接数只有10个,这时我们必须控制只有10个线程同时获取数据库连接保存数据,否则会报错无法获取数据库连接。如下列代码:
public class SemaphoreTest {
// 总线程数
private static final int THREAD_COUNT = 30;
// 通过线程池创建 30个线程
private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);
// 初始化 信号量Semaphore(10)表示允许10个线程获取许可证,也就是最大并发数是10
private static Semaphore 、 s = new Semaphore(10);
public static void main(String[] args) {
for (int i = 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();
}
}
另外,Semaphore还有一些功能,如:
/*
* 3、尝试获取许可,获取不到不执行
*/
try {
if (semaphore.tryAcquire()) {
test(threadNum);
semaphore.release();
}
} catch (Exception e) {
log.error("exception", e);
}
/*
* 4、尝试获取许可一段时间,获取不到不执行
* 参数1:等待时间长度 参数2:等待时间单位
*/
try {
if (semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS)) {
test(threadNum);
semaphore.release();
}
} catch (Exception e) {
log.error("exception", e);
}
参考:
三. CyclicBarrier:同步屏障
CyclicBarrier它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。
CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。
public class CyclicBarrierTest {
// 如果将 2 改为3 时,则主线程和 创建的线程会永远阻塞,不会有输出内容
static CyclicBarrier 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 {
// 主线程也阻塞,因为屏障设置的线程数为2,所以两个线程都同时开始执行,输入结果可能为 1 2 或者 2 1
c.await();
} catch (Exception e) {
}
System.out.println(2);
}
}
四. Exchanger: 交换者
以下部分内容引自《java 并发编程的艺术》
Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。
Exchanger可以用于遗传算法,遗传算法里需要选出两个人作为交配对象,这时候会交换
两人的数据,并使用交叉规则得出2个交配结果。Exchanger也可以用于校对工作,比如我们需要将纸制银行流水通过人工的方式录入成电子银行流水,为了避免错误,采用AB岗两人进行录入,录入到Excel之后,系统需要加载这两个Excel,并对两个Excel数据进行校对,看看是否录入一致,代码:
public class ExchangerTest {
private static final Exchanger<String> exgr = new Exchanger<String>();
private static ExecutorService threadPool = Executors.newFixedThreadPool(2);
public static void main(String[] args) {
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
String A = "银行流水B";// A录入银行流水数据
exgr.exchange(A);
} catch (InterruptedException e) {
}
}
});
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
String B = "银行流水B";// B录入银行流水数据
//交换数据 这里得到的 A 是 另外一个线程的
// 这里的参数只要是字符串就可以
String A = exgr.exchange("HAHAH");
// 输出为:A和B数据是否一致:false,A录入的是:银行流水A,B录入是:银行流水B
System.out.println("A和B数据是否一致:" + A.equals(B) + ",A录入的是:" + A + ",B录入是:" + B);
} catch (InterruptedException e) {
}
}
});
threadPool.shutdown();
}
}