1. 什么是控制并发流程?
2. CountDownLatch
3. Semaphore
4. Condition
5. CyclicBarrier
类 | 作用 | 说明 |
---|---|---|
Semaphore | 信号量,可以通过控制"许可证"的数量,来保证线程之间的配合 | 线程只有在拿到"许可证"后才能继续运行。相比于其他的同步器,更灵活 |
CyclicBarrier | 线程会等待,直到足够多线程达到了事先规定的数目。一旦达到触发条件,就可以进行下一步的动作 | 适用于线程之间相互等待处理结果就绪的场景 |
Phaser | 和CycliBarrier类似,但是计数可变 | Java 7 加入的 |
CountDownLatch | 和CyclicBarrier类似,数量递减到0时,触发动作 | 不可重复使用 |
Exchanger | 让两个线程在合适时交换对象 | 适用场景:当两个线程工作在同一个类的不同实例上时,用于交换数据 |
Condition | 可以控制线程的"等待"和"唤醒" | 是Object.wait()的升级版 |
2.1 CountDownLatch类的作用
并发流程控制的工具
类的主要方法介绍
2.2 两个典型用法
用法一: 一个线程等待多个线程都执行完毕,再继续自己的工作
/**
* 描述: 工厂中,质检,5个工人检查,所有人都认为通过,才通过
*/
public class CountDownLatchDemo1 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(5);
ExecutorService service = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
final int no = i + 1;
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep((long) (Math.random() * 10000));
System.out.println("No." + no + "完成了检查");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}
};
service.submit(runnable);
}
System.out.println("等待5个人检查完...");
latch.await();
System.out.println("所有人都完成了工作,进入下一个环节");
}
}
public class CountDownLatchTest {
private static CountDownLatch countDownLatch = new CountDownLatch(5);
/**
* Boss线程,等待员工到达开会
*/
static class BossThread extends Thread {
@Override
public void run(){
System.out.println("Boss在会议室等待,总共有:" + countDownLatch.getCount() + "个人开会...");
try {
// Boss等待
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("所有人都已经到齐了, 开会啦");
}
}
// 员工到达会议室线程
static class EmployeeThread extends Thread{
@Override
public void run(){
System.out.println(Thread.currentThread().getName() + ",到达会议室...");
// 员工到达会议室 count - 1
countDownLatch.countDown();
}
}
public static void main(String[] args) {
// Boss线程启动
new BossThread().start();
for (int i = 0; i < countDownLatch.getCount(); i++) {
new EmployeeThread().start();
}
}
}
用法二: 多个线程等待某一个线程的信号,同时开始执行
/**
* 描述: 模拟100米跑步,5名选手都准备好了,只等裁判员一声令下,所有人同时开始跑步
*/
public class CountDownLatchDemo2 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch begin = new CountDownLatch(1);
ExecutorService service = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
final int no = i + 1;
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("No." + no + "准备完毕,等待发令枪");
try {
begin.await();
System.out.println("No." + no + "开始跑步了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
service.submit(runnable);
}
// 裁判员检查发令枪...
Thread.sleep(5000);
System.out.println("发令枪响,比赛开始!");
begin.countDown();
}
}
综合运用: 一对多和多对一
/**
* 多等1, 1等多; 描述: 模拟100米跑步,5名选手都准备好了,只等裁判员一声令下,所有人同时开始跑步
* 当所有人到终点后,比赛才结束
*/
public class CountDownLatchDemo1And2 {
public static void main(String[] args) throws InterruptedException {
// 所有运动员等待裁判员发枪
CountDownLatch begin = new CountDownLatch(1);
// 裁判员在终点等待所有运动员到达
CountDownLatch end = new CountDownLatch(5);
ExecutorService service = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
final int no = i + 1;
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("No." + no + "准备完毕,等待发令枪");
try {
begin.await();
System.out.println("No." + no + "开始跑步了");
Thread.sleep((long)(Math.random()*1000));
System.out.println("No." + no + "跑到终点了");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
end.countDown();
}
}
};
service.submit(runnable);
}
// 裁判员检查发令枪...
Thread.sleep(5000);
System.out.println("发令枪响,比赛开始!");
begin.countDown();
end.await();
System.out.println("所有人到达终点,比赛结束");
}
}
两个典型用法: 一等多和多等一
CountDownLatch类在创建实例的时候,需要传递倒数次数。倒数到0的时候,之前等待的线程会继续运行
CountDownLatch内部通过共享锁实现
count
,该参数为计数器的初始值,也可以理解为该共享锁可以获取的总次数#await()
方法时,则执行释放共享锁状态,使count
值-1count
参数,必须要有count
线程调用#countDown()
方法,才会使计数器count
等于0,锁才会释放,前面等待的线程才会继续运行3.1 信号量使用流程
3.2 信号量主要方法介绍
3.3 案例演示
/**
* 描述: 演示Semaphore用法
*/
public class SemaphoreDemo {
static Semaphore semaphore = new Semaphore(3, true);
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(50);
for (int i = 0; i < 100; i++) {
service.submit(new Task());
}
service.shutdown();
}
static class Task implements Runnable {
@Override
public void run() {
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到了许可证");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "释放了许可证");
semaphore.release();
}
}
}
停车场案例
public class SemaphoreTest {
static class Parking {
// 信号量
private Semaphore semaphore;
Parking(int count) {
semaphore = new Semaphore(count);
}
public void park(){
try {
// 获取信号量
semaphore.acquire();
long time = (long)(Math.random() * 1000);
System.out.println(Thread.currentThread().getName() + "进入停车场,停车" + time + "毫秒...");
Thread.sleep(time);
System.out.println(Thread.currentThread().getName() + "开出停车场");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}
}
static class Car extends Thread {
Parking parking;
Car(Parking parking) {
this.parking = parking;
}
@Override
public void run() {
parking.park(); // 进入停车场
}
}
public static void main(String[] args) {
Parking parking = new Parking(3);
for (int i = 0; i < 5; i++) {
new Car(parking).start();
}
}
}
3.5 注意点
4.1 Condition作用
condition.await()
方法,一旦执行了await()
方法,线程就会进入阻塞状态condition.singnal()
方法,这时JVM就会从被阻塞的线程里找,找到那些等待该condition的线程,当线程1就会收到可执行信息的时候,它的线程状态就会变成Runnable可执行状态4.2 signalAll()和sigal()区别
4.3 案例演示
public class ConditionDemo1 {
private ReentrantLock lock = new ReentrantLock();
// 绑定在锁上面的
private Condition condition = lock.newCondition();
void method1(){
lock.lock();
try {
System.out.println("条件不满足,开始await");
condition.await();
System.out.println("条件满足了,开始执行后续的任务");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
void method2(){
lock.lock();
try {
System.out.println("准备工作完成,唤醒其它线程");
condition.signal();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ConditionDemo1 conditionDemo1 = new ConditionDemo1();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
conditionDemo1.method2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
conditionDemo1.method1();
}
}
生产者消费者模式
/**
* 描述: 演示用Condition实现生产者消费者模式
*/
public class ConditionDemo2 {
private int queueSize = 10;
private PriorityQueue<Integer> queue = new PriorityQueue<Integer>();
private Lock lock = new ReentrantLock();
// 未满,交给生产者使用
private Condition notFull = lock.newCondition();
// 非空,交给消费者使用
private Condition notEmpty = lock.newCondition();
public static void main(String[] args) {
ConditionDemo2 conditionDemo2 = new ConditionDemo2();
Producer producer = conditionDemo2.new Producer();
Consumer consumer = conditionDemo2.new Consumer();
producer.start();
consumer.start();
}
// 消费者
class Consumer extends Thread {
@Override
public void run(){
consume();
}
private void consume() {
while(true) {
lock.lock();
try {
while(queue.size() == 0) {
System.out.println("队列空,等待数据");
notEmpty.await();
}
queue.poll();
notFull.signalAll();
System.out.println("从队列里取走了一个数据,队列剩余" + queue.size() + "个元素");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
// 生产者
class Producer extends Thread {
@Override
public void run(){
produce();
}
private void produce() {
while(true) {
lock.lock();
try {
while(queue.size() == queueSize) {
System.out.println("队列满,等待有空余");
notFull.await();
}
queue.offer(1);
notEmpty.signalAll();
System.out.println("向队列插入了一个元素,队列剩余空间" + (queueSize - queue.size()));
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
}
4.4 Condition注意点
5.1 案例演示
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() {
@Override
public void run() {
System.out.println("所有人都到场了,大家统一出发!");
}
});
for (int i = 0; i < 5; i++) {
new Thread(new Task(i, cyclicBarrier)).start();
}
}
static class Task implements Runnable {
private int id;
private CyclicBarrier cyclicBarrier;
public Task(int id, CyclicBarrier cyclicBarrier) {
this.id = id;
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println("线程" + id + "现在前往集合地点");
try {
Thread.sleep((long)(Math.random() * 1000));
System.out.println("线程" + id + "到了集合地点,开始等待其他人到达");
cyclicBarrier.await();
System.out.println("线程" + id +"出发了");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}
5.2 CyclicBarrier和CountDownLatch的区别
作用不同: CyclicBarrier要等固定数量的线程都到达了栅栏位置才能继续执行,而CountDownLatch只需等待数字到0,也就是说,CountDownLatch用于事件,但是CyclicBarrier是用于线程的。
可重用性不同: CountDownLatch在倒数到0并触发门闩打开后,就不能再次使用了,除非新建新的实例;而CyclicBarrier可以重复使用
摘要: 慕课网悟空老师课程~~