【Beautiful JUC Part.9】控制并发流程 线程间的协调人
【Beautiful JUC Part.9】控制并发流程 线程间的协调人
一、什么是控制并发流程?
控制能发流程的工具类,作用就是帮助我们程序员更容易得让线程之间合作。
让线程之间相互配合,来满足业务逻辑
比如让线程A等待线程B执行完毕后再执行等合作策略
二、CountDownLatch倒计时门闩
1、CountDownLatch类的作用
CountDown的意思是倒数,而Latch的意思是门闩,倒数门闩。
例子:购物拼团,大巴(游乐园坐过山车排队),人满发车。
流程:倒数结束会签,一直处于等待的状态,直到倒计时结束了,此线程才继续工作。
2、类的主要方法结介绍
图解CountDownLatch
Ta调用await()方法,但是T1、T2、T3并没有await(),所以这三个方法会继续执行,直到等到计数为0的时候,然后Ta开始执行。
3、用法一:一个线程等待多个线程
用法描述:一个线程等待多个线程都执行完毕,再继续自己的工作。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 描述:工厂中,质检,5个工人检查,所有人都认为通过,才通过
*/
public class CountDownLatchDemo1 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(5);
ExecutorService executorService = 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();
}
}
};
executorService.submit(runnable);
}
System.out.println("等待5个人检查完...");
latch.await();
System.out.println("所有人都完成了工作,进入下一个环节。");
}
}
4、用法二:多个线程等待一个线程
用法描述:运动会时候,多个运动员等待发令员发令,多个线程等待一个线程。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 描述:模拟100米跑步,5名选手都准备好了,只等裁判员一声令下,所有人同时开始跑步
*/
public class CountDownLatchDemo2 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
final int no = i + 1;
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
System.out.println("No." + no + "准备完毕,等待发令枪");
latch.await();
System.out.println("No." + no + "出发!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
executorService.submit(runnable);
}
Thread.sleep((long) (Math.random() * 5000));
System.out.println("发令员开始倒计时");
for (int i = 3; i > 0; i--) {
System.out.println(i);
if (i == 1)
System.out.println("pong!!!!!!!!!!!!!!!!!!!!!!!");
Thread.sleep(1000);
latch.countDown();
}
}
}
三、Semaphore信号量
1、使用流程
初始化Semaphore并指定许可证的数量
在需要被现在的代码前加acquire()或者acquireUniterruptily()方法
在任务执行结束后,调用release()来释放许可证
主要方法
new Semaphore(int permits, boolean fair):这里可以设置是否要公平策略,如果传入true,那么Semaphore会把之前等待的线程放到FIFO的队列里,以便于当有了新的许可证,可以分发给之前等了最长时间的线程。
acquire()
acquireUniterruptibly()
tryAcquire():看看现在有没有空闲的许可证,如果有的话就获取,如果没有的话也没关系,我不必陷入阻塞,我可以去做别的事,过一会儿再来查看许可证的空闲情况。
tryAcquire(timeout):和tryAcquire()一样,但是多了一个超时时间,比如“在3秒内获取不到许可证,我就去做别的事”
release():归还许可证
2、Semaphore用法
基本用法
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* 描述:演示Semaphore用法
*/
public class SemaphoreDemo {
static Semaphore semaphore = new Semaphore(3, true);
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(50);
for (int i = 0; i < 100; i++) {
executorService.submit(new Task());
}
executorService.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().getName() + "释放了许可证");
semaphore.release();
}
}
}
还可以在acquire的时候,加入参数
比如一次直接拿3个信号量,然后释放的时候也释放3个,灵活多变,使得线程有了权重。
注意点
四、Condition接口(条件对象)
1、Condition的作用
当线程1需要等待某个条件的时候,它就去执行condition.await()方法,一旦执行了await()方法,线程就会进入阻塞状态。
然后通常会有另外一个线程,假设是线程2,去执行对应的条件,直到这个条件达成的时候,线程2就回去执行condition.signal()方法,这时JVM就会从被阻塞的线程里找到那些该condition的线程,当线程1收到可执行信号的时候,它的线程状态就会变成Runnable可执行状态。
signalAll()和signal()区别
2、代码演示
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 描述:演示Condition的基本用法
*/
public class ConditionDemo1 {
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
void method1() throws InterruptedException {
lock.lock();
try {
System.out.println("条件不满足,开始await");
condition.await();
System.out.println("条件满足了,开始执行后续的任务.....");
}finally {
lock.unlock();
}
}
void method2() {
lock.lock();
try {
System.out.println("准备工作完成,唤醒其他的线程");
condition.signal();
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ConditionDemo1 conditionDemo1 = new ConditionDemo1();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
conditionDemo1.method2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
conditionDemo1.method1();
}
}
3、实现生产者消费者模式
import java.util.PriorityQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 描述:演示用Condition实现生产者消费者模式
*/
public class ConditionDemo2 {
private int queueSize = 10;
private PriorityQueue<Integer> queue = new PriorityQueue<>(queueSize);
private Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
class Consumer extends Thread {
@Override
public void run() {
consume();
}
public void consume() {
while(true) {
lock.lock();
try {
while (queue.size() == 0) {
System.out.println("队列空,等待数据");
try {
notEmpty.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.poll();
notFull.signalAll();
System.out.println("从队列里取走了一个数据,队列剩余" + queue.size() + "个元素");
} 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("队列满,等待有空余");
try {
notFull.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.offer(1);
notEmpty.signalAll();
System.out.println("向队列插入了一个元素,队列剩余空间" + (queueSize - queue.size()));
} finally {
lock.unlock();
}
}
}
}
public static void main(String[] args) {
ConditionDemo2 conditionDemo2 = new ConditionDemo2();
Producer producer = conditionDemo2.new Producer();
Consumer consumer = conditionDemo2.new Consumer();
producer.start();
consumer.start();
}
}
4、Condition注意点
五、CylicBarrier循环栅栏
1、案例演示:等人出发
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* 描述:演示CyclicBarrier
*/
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() * 10000);
System.out.println("线程" + id + "到了集合地点,开始等待其他人到达");
cyclicBarrier.await();
System.out.println("线程" + id + "出发了");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}
2、和CountDownLatch的区别
作用不同:CyclicBarrier要等固定数量的线程都达到了栅栏位置才能继续执行,而CountDownLatch只需等待到0,也就是说,CountDownLatch用于事件,但是CyclicBarrier是用于线程的。
可重用性不同:CountDownLatch在倒数到0并出发门闩打开后,就不能再次使用了,除非新建新的实例;而CyclicBarrier可以重复使用。
所有线程都到了之后,可以允许一个功能,运行一个Runnable。