1. 简介#
Java中常见的四种并发工具类:
- CountDownLatch(闭锁,我觉得叫门闩更好理解)
- CyclicBarrier
- Semaphore
- Exchanger
2. 主线程等待子线程之CountDownLatch#
- 原理
CountDownLatch允许一个或多个线程等待其他一组线程完成操作,再继续执行。
CountDownLatch的构造函数传入一个int类型的参数N作为计数器,这个参数N作为需要等待的线程的数量。当调用CountDownLatch的countDown方法时N减1, CountDownLatch的await方法会阻塞当前线程(即主线程在闭锁上等待),直到N减为零,此时闭锁的主线程继续执行任务。 - 示例图
- 使用场景
超市的某一种商品,当促销时往往会有多人从货架上取出商品,只到该商品销售完后,促销结束。
import java.util.concurrent.CountDownLatch; public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(5); System.out.println("商品货源充足,促销开始!"); for (int i = 1; i <= 5; i++) { new Thread(() -> { System.out.println("商品编号[" + Thread.currentThread().getName() + "]已售出"); countDownLatch.countDown(); }, String.valueOf(i)).start(); } countDownLatch.await(); System.out.println("商品已售罄,促销结束!"); } }
商品货源充足,促销开始! 商品编号[2]已售出 商品编号[3]已售出 商品编号[1]已售出 商品编号[4]已售出 商品编号[5]已售出 商品已售罄,促销结束!
3. 同步到达屏障之CyclicBarrier(栅栏)
- 原理
让一组线程在到达一处屏障(即同步点)时被阻塞,直到最后一个线程达到屏障点,所有被阻塞的线程被释放继续执行。
CyclicBarrier的构造函数传入一个int类型的参数N作为计数器,这个参数N作为需要等待的线程的数量。每个线程调用CyclicBarrier的await方法使自己被阻塞,当N个线程调用了await方法后,所有线程停止等待,继续执行。 - 与CountDownLatch对比
相同点:都需要等待线程然后才能继续执行。
不相同点:可以循环使用。假如,将CyclicBarrier的计数器设置为10,当等待到10个线程后,计数器归0,然后可以继续等待10个线程。 - 示例图
- 使用场景
班主任收集学生信息,当全班同学的信息收集完成后,才可以交给教务处。
import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class CyclicBarrierDemo { public static void main(String[] args) { CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> { System.out.println("收集信息完成,转交教务处!"); }); for (int i = 1; i <= 5; i++) { new Thread(() -> { System.out.println("收集到学生[" + Thread.currentThread().getName() + "]的信息"); try { cyclicBarrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } }, String.valueOf(i)).start(); } } }
收集到学生[1]的信息 收集到学生[5]的信息 收集到学生[3]的信息 收集到学生[2]的信息 收集到学生[4]的信息 收集信息完成,转交教务处!
4. 控制并发线程数之Semaphore(信号量)
- 原理
JDK1.5就提供了Semaphore来统计一定数量的线程获得许可而对公共资源的访问。
Semaphore的构造函数入一个int类型的参数N作为可颁发的许可数量,线程通过调用acquire方法获取许可,只有获得许可的线程才可以执行任务,执行完成后调用release方法归还许可。当线程获取许可失败时,表示已经到达了最大的并发线程数,即许可已经颁发完毕,需要等待有线程归还后再次尝试获取。 - 示例图
- 使用场景
餐厅只有两个位置可以就餐,每个人需要就餐时,需要先看位置是否为空。为空时可直接就餐,不为空时则等待。
import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; public class SemaphoreDemo { public static void main(String[] args) { Semaphore semaphore = new Semaphore(2); for (int i = 1; i <= 5; i++) { new Thread(() -> { try { semaphore.acquire(); TimeUnit.SECONDS.sleep(3); System.out.println("顾客[" + Thread.currentThread().getName() + "]就餐,时间为3秒钟"); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); } }, String.valueOf(i)).start(); } } }
顾客[1]就餐,时间为3秒钟 顾客[2]就餐,时间为3秒钟 顾客[4]就餐,时间为3秒钟 顾客[3]就餐,时间为3秒钟 顾客[5]就餐,时间为3秒钟
5. 交换数据之Exchanger#
- 原理
Exchanger是一个线程间协作的工具类,用于线程间的数据交换。线程A调用exchange方法到达同步点,等待数据交换,线程B也调用exchange方法到达同步点,此时线程A和线程B进行数据交换。 - 示例图
- 使用场景
幼儿园进行玩具分享会,当两个小朋友都原因交换玩具时,进行交换玩具。
import java.util.concurrent.Exchanger; public class ExchangerDemo { public static void main(String[] args) { Exchanger<String> exchanger = new Exchanger<>(); for (int i = 1; i <= 6; i++) { new Thread(() -> { final String treadName = Thread.currentThread().getName(); try { String data = "[物体" + treadName + "]"; System.out.println("小朋友[" + treadName + "]交换前拥有" + data); data = exchanger.exchange(data); System.err.println("小朋友[" + treadName + "]交换后拥有" + data); } catch (InterruptedException e) { e.printStackTrace(); } }, String.valueOf(i)).start(); } } }
小朋友[1]交换前拥有[物体1] 小朋友[3]交换前拥有[物体3] 小朋友[6]交换前拥有[物体6] 小朋友[4]交换前拥有[物体4] 小朋友[2]交换前拥有[物体2] 小朋友[5]交换前拥有[物体5] 小朋友[3]交换后拥有[物体1] 小朋友[1]交换后拥有[物体3] 小朋友[4]交换后拥有[物体6] 小朋友[6]交换后拥有[物体4] 小朋友[5]交换后拥有[物体2] 小朋友[2]交换后拥有[物体5]