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]