文章目录
1、什么是控制并发流程
1.1为什么要控制?
- 线程默认是由线程调度器执行的,但是有些时候我们想指定线程的执行顺序。
1.2、线程并发控制工具类概览
- 控制并发流程的工具类,作用就是帮助我们让线程之间合作。让线程之间相互配合,来满足业务逻辑。
2、CountDownLatch倒计时门闩(多个线程可并行)
2.1介绍
- eg:大巴,人满发车。
- 流程:倒计时结束之前,一直处于等待状态,直到倒计时结束了,这个线程才继续工作。
2.2主要方法
- CountDownLatch(int count):仅有一个构造函数,参数count为需要倒数的数值。
- await():调用await()方法的线程会被挂起(等待),直到count为0才继续执行。
- countDown():将count值减一,直到为0时,等待的线程会被唤醒。
2.3 用法
- 用法一:一个线程等待多个线程执行完毕,再继续自己的工作。
/**
* 描述: 工厂中,质检,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("所有人都完成了工作,进入下一个环节。");
}
}
-
用法二:一个线程执行完,其它线程才能执行。
/** * 描述: 模拟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() * 10000)); 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("所有人到达终点,比赛结束"); }}
3、Semaphore信号量(保证同一时间内线程访问数量小于某个值)。
3.1解释
- Semaphore可以用来限制或者管理数量有限的资源的使用情况
- eg:污染不能太多,污染许可证只能发3张。要想排污之前必须获得许可证。每一年再更替。
3.2使用流程
1、初始化Semaphore,并指定许可证数量。
2、在需要被现在的代码前加acquire()或者acquireUninterruptibly()方法。
3、在任务执行结束后,调用release()来释放许可证。
3.3主要方法介绍。
- new Semaphore(int permits,boolean fair):这里可以设置是否要采用公平策略。
- acquire():获取许可证,不能被中断
- acquireUninterruptibly()获取许可证,可以被中断。
- tryAcquire(): 看看现在有没有空闲的许可证,如果有的话就获取。没有的话不会陷入阻塞状态,可以去做其它的事情,过一会再来看许可证的空闲情况。
- tryAcquire(timeout)
3.4 代码演示(释放和获取的数量必须要一致)
/**
* 描述: 演示Semaphore用法
*/
public class SemaphoreDemo {
static Semaphore semaphore = new Semaphore(5, 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(3);
} 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(2);
}
}
}
4、Condition接口(又称条件对象)
4.1作用
- 当线程1需要等待某个条件的时候**(例如等待许可证),它就去执行condition.await()方法,一旦执行了await**()方法,线程就会进入阻塞状态。
- 通常会有另一个线程2,去执行另外一个条件,直到条件达成时,线程2就会去执行condition.signal()方法(singalAll()方法会唤起所有的在等待的线程),这时JVM会去被阻塞的线程中找,超导那些等待该condition的线程,当线程1收到可执行信号的时候,它的状态就会编程runnable可执行状态。
4.2代码演示
/**
* 描述: 演示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(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>(queueSize); 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("队列空,等待数据"); try { notEmpty.await(); } catch (InterruptedException e) { e.printStackTrace(); } } queue.poll(); notFull.signal(); 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.signal(); System.out.println("向队列插入了一个元素,队列剩余空间" + (queueSize - queue.size())); } finally { lock.unlock(); } } } } }
4.3注意点
- 如果说Lock是用来代替synchronized,Condition就是用来代替相对应的Object.await/notify方法的,作用几乎差不多。
- await方法会自动释放持有的Lock锁,不需要手动释放。
- 调用await的时候,必须持有锁,否则会抛出异常,和Object.wait一样。
5、CyclicBarrier循环栅栏。
-
CyclicBarrier循环栅栏和CountDownLatch很类似,都能阻塞一组线程。
-
当有大量的线程相互配合,分别计算不同任务,最后需要同一汇总的时候,我们可以使用**CyclicBarrier。**当某一个线程执行完毕,它就会到集合点等待,直到所有的线程都到了集合点,这个栅栏就会被撤销。
-
可重用性:CountDownLatch在倒数到0并出发门闩打开后就不能再次使用了,除非创建新的实例。而CyclicBarrier可以重复使用
-
CyclicBarrier要等固定数量的线程到了栅栏**(用于线程)。但是CountDownLatch只需要等待数字变为0(用于事件)**.
-
代码演示
/** * 描述: 演示CyclicBarrier */ public class CyclicBarrierDemo { public static void main(String[] args) { CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() { @Override public void run() { System.out.println("所有人都到场了, 大家统一出发!"); } }); //改为10的时候,到了五个就发车 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 e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } } }
线程0现在前往集合地点
线程3现在前往集合地点
线程2现在前往集合地点
线程1现在前往集合地点
线程4现在前往集合地点
线程1到了集合地点,开始等待其他人到达
线程4到了集合地点,开始等待其他人到达
线程3到了集合地点,开始等待其他人到达
线程2到了集合地点,开始等待其他人到达
线程0到了集合地点,开始等待其他人到达
所有人都到场了, 大家统一出发!
线程0出发了
线程1出发了
线程4出发了
线程2出发了
线程3出发了Process finished with exit code 0