并发流程的控制(CountDownLatch、Semaphore、Condition、CycliBarrier)

概览

在这里插入图片描述

CountDownLatch

CountDownLatch 类在创建实例的时候,需要传递倒数次数。倒数到 0 的时候,之前等待的线程会继续运行,并且 CountDownLatch 不能回滚重置

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

await():调用 await() 方法的线程会被挂起,它会等待直到 count 值为 0 才继续执行。

countDown():将 count 值减1,直到为 0 时,等待的线程会被唤起。

在这里插入图片描述
CountDownLatch是不能够重用的,如果需要重新计数,可以考使用CyclicBarrier或者创建新的CountDownlatch实例。

用法一

一个线程等待多个线程都执行完毕再继续自己的工作。

/**
 * 描述:工厂中,质检,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("所有人都完成了工作,进入下一个环节");
    }
}
等待5个人检查完.....
No.4完成了检查。
No.5完成了检查。
No.3完成了检查。
No.2完成了检查。
No.1完成了检查。
所有人都完成了工作,进入下一个环节

用法二

多个线程等待某一个线程的信号 ,同时开始执行。

/**
 * 描述:模拟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();
    }
}

No.1准备完毕,等待发令枪
No.5准备完毕,等待发令枪
No.4准备完毕,等待发令枪
No.3准备完毕,等待发令枪
No.2准备完毕,等待发令枪
发令枪响,比赛开始!
No.5开始跑步了
No.1开始跑步了
No.4开始跑步了
No.3开始跑步了
No.2开始跑步了

用法三

一等多和多等一结合起来,运动员等待裁判员发枪,裁判员等待五个运动员跑到终点

public class CountDownLatchDemo2 {
    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("所有人到达终点");
    }
}
No.1准备完毕,等待发令枪
No.3准备完毕,等待发令枪
No.2准备完毕,等待发令枪
No.5准备完毕,等待发令枪
No.4准备完毕,等待发令枪
发令枪响,比赛开始!
No.1开始跑步了
No.3开始跑步了
No.2开始跑步了
No.5开始跑步了
No.4开始跑步了
No.1跑到终点了
No.2跑到终点了
No.3跑到终点了
No.5跑到终点了
No.4跑到终点了
所有人到达终点

Semaphore

Semaphore 可以用来限制或管理数量有限的资源的使用情况。

信号量的作用是维护一个“许可证”的计数,线程可以“获取"许可证,那信号量剩余的许可证就减一,线程也可以“释放”一个许可证,那信号量剩余的许可证就加一,当信号量所拥有的许可证数量为 0,那么下一个还想要获取许可证的线程,就需要等待,直到有另外的线程释放了许可证

主要方法

  1. new Semaphore(int permits, boolean fair):这里可以设置是否要使用公平策略,如果传入true,那么 Semaphore 会把之前等待的线程放到 FIFO 的队列里,以便于当有了新的许可证,可以分发给之前等了最长时间的线程。

  2. acquire():可以响应中断

  3. acquireUninterruptibly()

  4. tryAcquire():看看现在有没有空闲的许可证,如果有的话就获取,如果没有的话也没关系,我不必陷入阻塞,我可以去做别的事,过一会再来查看许可证的空闲情况。

  5. tryAcquire(timeout):和 tryAcquire() 一样,但是多了一个超时时间,比如“在3秒内获取不到许可证,我就去做别的事”x

  6. release():必须调用此方法,归还许可证。获取和释放的许可证数量必须一致,否则比如每次都获取 2 个但是只释放 1 个甚至不释放,随着时间的推移,到最后许可证数量不够用,会导致程序卡死。( 虽然信号量类并不对是否和获取的数量做规定,但是这是我们的编程规范,否则容易出错)

信号量使用流程

  1. 初始化 Semaphore 并指定许可证的数量
  2. 在需要被现在的代码前加 acquire() 或者 acquireUninterruptibly() 方法
  3. 在任务执行结束后,调用 release() 来释放许可证

代码演示

注意在初始化 Semaphore 的时候设置公平性,一般设置为 true 会更合理

并不是必须由获取许可证的线程释放那个许可证,事实上获取和释放许可证对线程并无要求,也许是A获取了 ,然后由B释放,只要逻辑合理即可。

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

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(3);
        }
    }
}

pool-1-thread-2拿到了许可证
pool-1-thread-2释放了许可证
pool-1-thread-1拿到了许可证
pool-1-thread-1释放了许可证
pool-1-thread-3拿到了许可证
pool-1-thread-3释放了许可证
pool-1-thread-4拿到了许可证
pool-1-thread-4释放了许可证
pool-1-thread-5拿到了许可证

Process finished with exit code -1

Condition

作用

当线程1需要等待某个条件的时候,它就去执行 condition.await() 方法,一旦执行了 await() 方法,线程就会进入阻塞状态

然后通常会有另外一个线程,假设是线程2 ,去执行对应的条件,直到这个条件达成的时候,线程2就会去执行
condition.signal)方法,这时JVM就会从被阻塞的线程里找,找到那些等待该condition的线程,当线程1就会收到可执行信号的时候,它的线程状态就会变成Runnable可执行状态

signalAll() 和 signal() 区别

signalAll() 会唤起所有的正在等待的线程,但是 signal() 是公平的,只会唤起那个等待时间最长的线程

代码演示

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();
    }
}
条件不满足,开始await
准备工作完成,唤醒其他的线程
条件满足了,开始执行后续的任务

Process finished with exit code 0

实现生产者消费者模式

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.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();
                }
            }
        }
    }

}

实际上,如果说Lock用来代替synchronized ,那么Condition就是用来代替相对应的Object.wait/notify的,所以在用法和性质上,几乎都一样

await 方法会自动释放持有的 Lock 锁,和 Object.wait 一样,不需要自己手动先释放锁

调用 await 的时候,必须持有锁,否则会抛出异常,和 Object.wait 一样。

CycliBarrier

CyclicBarrier 循环栅栏和 CountDownLatch 很类似,都能阻塞一组线程

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

示例代码

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        // 所有线程都准备好后执行 Runanable 重写方法
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() {
            @Override
            public void run() {
                System.out.println("所有人都到场了, 大家统一出发!");
            }
        });
        for (int i = 0; i < 10; 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() {

            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();
            }
        }
    }
}
线程2到了集合地点,开始等待其他人到达
线程0到了集合地点,开始等待其他人到达
线程7到了集合地点,开始等待其他人到达
线程4到了集合地点,开始等待其他人到达
线程6到了集合地点,开始等待其他人到达
所有人都到场了, 大家统一出发!
线程6出发了
线程2出发了
线程0出发了
线程7出发了
线程4出发了
线程3到了集合地点,开始等待其他人到达
线程1到了集合地点,开始等待其他人到达
线程9到了集合地点,开始等待其他人到达
线程8到了集合地点,开始等待其他人到达
线程5到了集合地点,开始等待其他人到达
所有人都到场了, 大家统一出发!
线程5出发了
线程3出发了
线程1出发了
线程8出发了
线程9出发了

Process finished with exit code 0

CyclicBarrier 和 CountDownLatch 的区别

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

可重用性不同:CountDownLatch 在倒数到 0 并触发门闩打开后,就不能再次使用了,除非新建新的实例,而 CyclicBarrier 可以重复使用。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值