JUC - 控制并发流程

1. 什么是控制并发流程

在我们不控制并发的时候,线程尽可能跑,受线程调度器控制,而不受程序员控制;如果我现在想让一些线程先执行,一些线程在最后执行,就要使用并发流程的工具类,让线程之间相互合作,来满足业务需求;比如让线程A等待线程B执行完再执行等策略

作用说明
Semaphore信号量,可以通过控制许可证的数量来保证线程之间的配合线程只有在拿到许可证后才能继续运行
CyclicBarrier线程会等待,直到足够多线程达到了事先规定的数目,一旦触发了条件就可以进行下一步动作适合线程之间互相等待处理结果就绪的场景
PhaserCyclicBarrier类似,但是计数可变
CountDownLatchCyclicBarrier类似,数量递减到0的时候触发动作不可重复使用
Exchanger让两个线程在合适时交换对象当两个线程工作在同一个类的不同实例上时,用于交换数据
Condition可以控制线程的等待和唤醒

2. CountDownLatch倒计时门闩

2.1 基本方法

CountDownLatch是一个计数器闭锁,通过它可以完成类似于阻塞当前线程的功能,即:一个线程或多个线程一直等待,直到其他线程执行的操作完成

//仅有一个构造函数,参数为需要倒数的数值
CountDownLatch(int count)

//调用await的线程会被挂起,等待直到count的值为0才继续执行,谁等待谁await
await()

//将count的值减1,直到为0,等待的线程将会被唤醒,谁倒数谁CountDown
CountDown()

在这里插入图片描述

2.2 用法① 一等多

一个线程去等待多个线程完成后才执行,假如有这样的场景,在质检线上有五个质检员,有一个产品只有等到这五个质检员都检查完成才会去进行下面的检查,这就可以使用CountDownLatch完成

public class CountDownDemo {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(5);
        //CountDownLatch
        CountDownLatch countDownLatch = new CountDownLatch(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+1));
                        Thread.sleep((long)(Math.random()*1000));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        countDownLatch.countDown();
                    }
                }
            };
            service.submit(runnable);
        }
        System.out.println("等待质检员检查完");
        countDownLatch.await();
        System.out.println("检查完了,进入下一个环节");

    }
}

2.3 用法② 多等一

多个线程等待统一的信号,然后让他们同时进行,比如模拟双十一的请求,让多个线程等待一个信号然后同时开始访问服务器

下面模拟一个例子:模拟100米跑步,5名选手都准备好了,只等裁判员发令,然后同时起跑

public class CountDownDemo2 {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(5);
        //CountDownLatch
        CountDownLatch countDownLatch = new CountDownLatch(1);

        for(int i = 0; i<5; i++){
            final int no = i+1;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println("准备完毕等待发令");
                    try {
                        countDownLatch.await();
                        System.out.println("运动员"+no+"开始跑步");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            service.submit(runnable);
        }
        System.out.println("等待裁判员....");
        Thread.sleep((long)(Math.random()*1000));
        System.out.println("开始");
        countDownLatch.countDown();


    }
}

2.4 好像用join也能实现?

让一个线程等待另一些线程执行完毕再执行好像用join也能达到目的,但是实际并非如此;
如果我们使用线程池来运行任务,任务虽然会结束,但是线程永远不会停止,所以这种情况就不适用与join

2.5 注意点

CounDownLatch是不可以重用的,如果需要重新计数可以考虑使用CyclicBarrier或者创建新的CountDownLatch

3. Semaphore信号量

3.1 基本方法

Semaphore用来限制或管理有限的资源的使用情况,信号量的作用是为维护一个许可证的计数,线程可以获取许可证,那信号量的许可证的总数就-1,线程也可以释放许可证,那信号量的许可证的总数就+1,当信号量所拥有的许可证的数量为0,接下来想获得许可证的线程就要等待别人释放

我们可以利用它来"限流"
在这里插入图片描述

//初始化信号量指定许可证的数量,也可以设置公平与否
new Semaphore(int permits, boolean fair)

//获取,可以中断
acquire()
//可以分配多个许可证,也就相当于现在的线程的权重大,需要的许可证更多
acquire(num)

//获取,不可中断
acquireUninterruptibly()

//尝试获取
tryAcquire()
tryAcquire(timeout)

//归还许可证
release()
//归还多个,对应前面一次获得了多个的情况
release(num)

3.2 基本用法

public class SemaphoreDemo {
    static Semaphore semaphore = new Semaphore(3);
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(50);
        for(int i = 0; i<50; i++){
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName()+"拿到许可证");
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        System.out.println(Thread.currentThread().getName()+"释放了许可证");
                        semaphore.release();
                    }
                }
            });
        }

    }
}

3.3 注意点

① 获取和是释放线程的许可证数量必须一致,否则比如每次获取两个但是只释放一个,随着时间的推移最后许可证数量不够用会导致程序卡死(这不是语法规定,只是编程规范)

② 在初始化的时候最好是公平的,避免饥饿

③ 释放和获取并是跨线程的,并不是必须由获得许可证的线程释放许可证,只要合理,也可以由其他线程释放

④ 信号量除了控制临界区最多可以同时N个线程访问外,另一个作用是实现条件等待,例如线程1需要在线程2完成准备工作再开始工作,那么就可以线程1acquire(),线程2完成任务后再release(),这样相当于一个轻量级的CountDownLatch

4. Condition条件对象

4.1 基本介绍

Condition是个接口又称为条件对象,假设线程1需要等待某个条件的时候,就去执行condition.await()方法,进入阻塞状态

然后线程2再执行对应的条件,满足条件后执行condition.signal()方法唤醒之前阻塞的线程
在这里插入图片描述

4.2 基本使用

public class ConditionDemo {
    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    void method1(){
        lock.lock();
        try {
            System.out.println("条件不满足,等待");
            condition.await();
            System.out.println("条件满足继续工作");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    void method2(){
        lock.lock();
        try {
            System.out.println("条件满足,唤醒其他线程");
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ConditionDemo conditionDemo = new ConditionDemo();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    conditionDemo.method2();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        conditionDemo.method1();
    }
}

4.3 实现生产者消费者模式

public class ConsumerAndProduce {
    private int queueSize = 20;//队列的满负荷容量
    private PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(queueSize);
    private Lock lock = new ReentrantLock();
    //队列空了,消费者没法消费
    private Condition empty = lock.newCondition();
    //队列满了,生产者不能生产
    private Condition full = lock.newCondition();

    class Consumer extends Thread {
        @Override
        public void run() {
            consume();
        }

        public void consume() {
            while (true) {
                lock.lock();
                try {
                    while (priorityQueue.size() == 0) {
                        try {
                            empty.await();
                        } catch (Exception e) {

                        }
                    }
                    priorityQueue.poll();
                    full.signalAll();
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    class Produce extends Thread{
        @Override
        public void run() {
            produce();
        }
        public void produce(){
            while (true){
                lock.lock();
                try {
                    while (priorityQueue.size() == queueSize){
                        try {
                            full.await();
                        }catch (Exception e){

                        }
                    }
                    priorityQueue.offer(1);
                    empty.signalAll();
                } finally {
                    lock.unlock();
                }
            }
        }
    }
}

4.4 注意点

  • 实际上,如果说Lock是用来代替synchronized,那么Condition就是用来代替对应的Object.wait()/notify()
  • await方法会自动释放持有的Lock锁,和Object.wait()一样不需要自己先手动释放锁
  • 调用await的适合,必须持有锁
  • 一个lock锁可以持有多个condition

5. CyclicBarrier循环栅栏

5.1 基本介绍

CyclicBarrierCountDownLatch很相似,都能阻塞一组线程
当有大量线程相互配合,分别计算不同任务,并且需要最后统一汇总的时候,我们可以使用CyclicBarrierCyclicBarrier可以构造一个集结点,当某一个线程执行完毕,他就会到集结点等待,直到所有的线程都到了集结点,那么该栅栏就会被撤销,所有线程再统一出发继续执行接下来的任务

比如5个小伙伴约好到校门口集合,大家都到了再做接下来的事情

public class CyclicBarrierDmo {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() {
            @Override
            public void run() {
                System.out.println("到齐了,做事");
            }
        });
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for(int i = 0; i<5; i++){
            final int no = i+1;
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep((long)(Math.random()*1000));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("小伙伴"+no+"到了");
                    try {
                        cyclicBarrier.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

    }
}

5.2 CyclicBarrierCountDownLatch

① 作用不同:
CyclicBarrier是等固定数量的线程到达栅栏位置才继续执行,而CountDownLatch只需要等待数字到0,也就是说CyclicBarrier是基于线程的,CountDownLatch是基于事件的

② 可重用性不同:
CountDownLatch不能重用,CyclicBarrier可以重用,用完计数回到初始值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值