0. 前言
并发包里提供了几个非常有用的并发工具类,CountDownLatch、CyclicBarrier、Semaphore 工具类提供了一种并发流程控制的手段,Exchanger 工具类则提供了在线程间交换数据的手段
1. 等待多线程完成的 CountDownLatch
CountDownLatch 允许一个或多个线程等待其他线程完成操作
1.1 重要方法
CountDownLatch(int count):构造方法 count 入参作为计数器countDown():计数器减 1await():线程一直被阻塞直到计数器为 0await(long timeout, TimeUnit unit):线程被阻塞一段时间后,超过指定时间后计数器还不为 0,则该线程不再阻塞而继续执行
1.2 案例
/**
* 使用多线程解析一个 Excel 里多个 sheet 的数据
* 主线程需要等待所有线程的解析完成后才继续执行
*/
public class Test {
public static CountDownLatch c = new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new ExcelRunner(3000));
Thread thread2 = new Thread(new ExcelRunner(5000));
thread1.start();
thread2.start();
c.await();
System.out.println("main end");
}
static class ExcelRunner implements Runnable {
private int parseTime;
public ExcelRunner(int parseTime) {
this.parseTime = parseTime;
}
@Override
public void run() {
try {
Thread.sleep(parseTime);
System.out.println(Thread.currentThread().getName() + " 解析完毕");
c.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2. 同步屏障 CyclicBarrier
CyclicBarrier 同步屏障让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,所有被阻塞的线程不再阻塞而继续执行。可用于多线程计算数据,最后合并计算结果
2.1 构造方法
CyclicBarrier(int parties)
CyclicBarrier(int parties, Runnable barrierAction)
-
parties:屏障阻塞的线程数
-
barrierAction:最后一个到达屏障的线程,优先执行完 barrierAction 任务后,所有被阻塞的线程才可以继续执行
2.2 重要方法
await():线程一直被屏障阻塞直到最后一个线程到达屏障await(long timeout, TimeUnit unit):线程被阻塞一段时间后,超过指定时间后最后一个线程还未到达屏障,则破坏屏障抛出异常
2.3 案例
/**
* 一个 Excel 保存了多个用户的银行流水的 sheet
* 需要统计每个 sheet 的的流水总和,再把所有 sheet 的流水总和加起来
*/
public class BankWaterService implements Runnable {
private int sheetNum;
private AtomicInteger sheetIndex = new AtomicInteger(1);
private CyclicBarrier c;
private ConcurrentHashMap<String, Integer> bankWaterMap = new ConcurrentHashMap<>();
public BankWaterService(int sheetNum) {
this.sheetNum = sheetNum;
this.c = new CyclicBarrier(sheetNum, this);
}
public void count() {
ExecutorService executor = Executors.newFixedThreadPool(sheetNum);
for (int i = 0;i < sheetNum;i++) {
executor.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
bankWaterMap.put("sheet" + sheetIndex.getAndIncrement(), 100);
c.await();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
executor.shutdown();
}
@Override
public void run() {
int total = 0;
for (Map.Entry<String, Integer> entry : bankWaterMap.entrySet()) {
total += entry.getValue();
}
bankWaterMap.put("total", total);
System.out.println(bankWaterMap);
}
}
public class Test {
public static void main(String[] args) {
BankWaterService service = new BankWaterService(10);
service.count();
}
}
2.4 CountDownLatch 和 CyclicBarrier 的区别
-
CountDownLatch 的计数器只能使用一次,CyclicBarrier 的计数器可以使用
reset()方法重置 -
CyclicBarrier 提供了更多有用的方法
-
getNumberWaiting()方法:获取屏障阻塞的线程数量 -
isBroken()方法:判断阻塞的线程是否被中断
-
3. 控制并发线程数的 Semaphore
Semaphore 用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用公共资源。可用于做流量控制,特别是公共资源有限的应用场景,比如数据库连接
3.1 构造方法
Semaphore(int permits)
Semaphore(int permits, boolean fair)
permits:允许同时并发执行的线程数量
fair:公平性,如果设为 true 时,则下次执行的是等待最久的线程
3.2 重要方法
acquire():阻塞线程获取执行许可release():线程释放执行许可
3.3 案例
/**
* 有多个文件需要读取后保存到数据库
* 因为都是 IO 密集型任务,所以开启多个线程并发读取后保存
* 由于数据库连接数量有限,所以需要控制同时保存数据的线程数量
*/
public class Test {
private static ExecutorService executor = Executors.newFixedThreadPool(30);
private static Semaphore s = new Semaphore(10);
public static void main(String[] args) {
for (int i = 0;i < 30;i++) {
executor.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + " parse data end");
s.acquire();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " save data end");
s.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
executor.shutdown();
}
}
4. 线程间交换数据的 Exchanger
Exchanger 是一个用于线程间协作的工具类,可用于线程间交换数据。假设有两个线程交换数据,Exchanger 提供一个同步点,当两个线程都到达同步点时,这个两个线程就可以将自身生产的数据传递给对方
4.1 重要方法
-
exchange(V x):线程等待另一个线程到达同步点时交换数据 -
exchange(V x, long timeout, TimeUnit unit):线程等待另一个线程到达同步点时交换数据,如果超过指定时间后另一个线程还未到达同步点,则放弃等待抛出异常
4.2 案例
/**
* 当银行将纸质的银行流水录入系统时,为了避免录入错误数据
* 需要 A、B 两名员工录入数据后,进行数据校对
*/
public class Test {
private static Exchanger<String> exchanger = new Exchanger();
public static void main(String[] args) {
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(4000);
String aData = "流水数据";
exchanger.exchange(aData);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
threadA.start();
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
try {
String bData = "流水数据";
String aData = exchanger.exchange(bData);
System.out.println("b员工校对结果 " + bData.equals(aData));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
threadB.start();
}
}
学自《Java并发编程的艺术》