1. CountDownLatch
在日常开发中经常会遇到需要在主线程中开启多个线程去并行执行任务,并且主线程需要等待所有子线程执行完毕后再进行汇总的场景。在CountDownLatch出现之前一般都使用线程的join()方法来实现这一点,但是join方法不够灵活,不能够满足不同场景的需要,所以JDK开发组提供了CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
工作原理
CountDownLatch是通过一个计数器来实现的,计数器的初始值为初始任务的数量。每当完成了一个任务后,计数器的值就会减1(CountDownLatch.countDown()方法)。当计数器值到达0时,它表示所有的已经完成了任务,然后在闭锁上等待CountDownLatch.await()方法的线程就可以恢复执行任务。
简单使用
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestCountDownLatch {
private static CountDownLatch countDownLatch = new CountDownLatch(2);
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
//将线程A添加到线程池
executorService.submit(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("ThreadA 执行任务完毕");
countDownLatch.countDown();
}
System.out.println("threadA over");
}
});
//将线程B添加到线程池
executorService.submit(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("ThreadB 执行任务完毕");
countDownLatch.countDown();
}
System.out.println("threadB over");
}
});
System.out.println("wait All child Thread Over!");
//等待子线程执行完毕,返回
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("All Child Thread over");
executorService.shutdown();
}
}
应用场景
开始执行前等待n个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统已经启动和运行了,例如处理excel中多个表单。
CountDownLatch和join的区别
调用一个子线程的join()方法后,该线程会一直阻塞直到子线程运行完毕,而CountDownLatch则使用计数器来允许子线程运行完毕或者在运行中递减计数,也就是CountDownLatch可以在子线程运行的任何时候让await方法返回而不一定必须等到线程结束。另外,使用线程池来管理线程时,一般都是直接添加Runnable到线程池,这时候就没有办法在调用线程的join方法了,也就是说CountDownLatch相比join方法让我们对线程同步有更加灵活的控制。
2. CyclicBarrier
工作原理
CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。
CyclicBarrier还提供一个更高级的构造函数CyclicBarrier(int parties,Runnable barrierAction),用于在线程到达屏障时,由最后一个到达屏障点的线程执行barrierAction,方便处理更复杂的业务场景。
简单使用
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestCyclicBarrier {
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(2,new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread() + "task merge result");
}
});
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
//将线程A添加到线程池
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread() + "task-1 enter in barrier");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "task-1 enter out barrier");
}
});
//将线程B添加到线程池
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread() + "task-2 enter in barrier");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "task-2 enter out barrier");
}
});
executorService.shutdown();
}
}
应用场景
CyclicBarrier可以用于多线程计算数据,最后合并计算结果的场景。
CountDownLatch和CyclicBarrier的区别
CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以反复使用。
CountDownLatch.await一般阻塞工作线程,所有的进行预备工作的线程执行countDown,而CyclicBarrier通过工作线程调用await从而自行阻塞,直到所有工作线程达到指定屏障,再大家一起往下走。
在控制多个线程同时运行上,CountDownLatch可以不限线程数量,而CyclicBarrier是固定线程数。
同时,CyclicBarrier还可以提供一个barrierAction,合并多线程计算结果。
3. Semaphore
Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。
工作原理
我们可以把它比作是控制流量的红绿灯,比如XX马路要限制流量,只允许同时有一百辆车在这条路上行使,其他的都必须在路口等待,所以前一百辆车会看到绿灯,可以开进这条马路,后面的车会看到红灯,不能驶入XX马路,但是如果前一百辆中有五辆车已经离开了XX马路,那么后面就允许有5辆车驶入马路,这个例子里说的车就是线程,驶入马路就表示线程在执行,离开马路就表示线程执行完成,看见红灯就表示线程被阻塞,不能执行。
简单使用
import java.util.concurrent.*;
public class TestSemaphore {
private static Semaphore semaphore = new Semaphore(2);
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 20; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"save data");
semaphore.release();
}
});
}
executorService.shutdown();
}
}
在代码中,虽然有10个线程在执行,但是只允许2个并发的执行。Semaphore的构造方法Semaphore(int permits) 接受一个整型的数字,表示可用的许可证数量。Semaphore(2)表示允许2个线程获取许可证,也就是最大并发数是2。Semaphore的用法也很简单,首先线程使用Semaphore的acquire()获取一个许可证,使用完之后调用release()归还许可证。还可以用tryAcquire()方法尝试获取许可证。
应用场景
Semaphore可以用于做流量控制,特别公用资源有限的应用场景,比如数据库连接。假如有一个需求,要读取几万个文件的数据,因为都是IO密集型任务,我们可以启动几十个线程并发的读取,但是如果读到内存后,还需要存储到数据库中,而数据库的连接数只有10个,这时我们必须控制只有十个线程同时获取数据库连接保存数据,否则会报错无法获取数据库连接。这个时候,我们就可以使用Semaphore来做流控。
4. Exchanger
Exchange(交换)是一个用于线程间协作的工具类。Exchange用于进行线程间的数据交换。它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。
工作原理
简单使用
package test2;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Exchanger;
/**
*类说明:演示Exchange用法
*/
public class UseExchange {
private static final Exchanger<Set<String>> exchange = new Exchanger<>();
public static void main(String[] args) {
new Thread(() -> {
Set<String> setA = new HashSet<>();//存放数据的容器
try {
setA.add("A1");
setA.add("A2");
setA = exchange.exchange(setA);//交换set
/*处理交换后的数据*/
} catch (InterruptedException e) {
}
for (String a:setA) {
System.out.println(a + "in setA");
}
}).start();
new Thread(() -> {
Set<String> setB = new HashSet<>();//存放数据的容器
try {
setB.add("B1");
setB.add("B2");
setB = exchange.exchange(setB);//交换set
/*处理交换后的数据*/
} catch (InterruptedException e) {
}
for (String b:setB) {
System.out.println(b +"in setB");
}
}).start();
}
}
应用场景
Exchanger 可能在应用程序(比如遗传算法和管道设计)中很有用。