千家信息网

怎么使用JUC中的Semaphore、CyclicBarrier、CountDownLatch

发表于:2024-11-23 作者:千家信息网编辑
千家信息网最后更新 2024年11月23日,这篇文章主要讲解了"怎么使用JUC中的Semaphore、CyclicBarrier、CountDownLatch",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研
千家信息网最后更新 2024年11月23日怎么使用JUC中的Semaphore、CyclicBarrier、CountDownLatch

这篇文章主要讲解了"怎么使用JUC中的Semaphore、CyclicBarrier、CountDownLatch",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"怎么使用JUC中的Semaphore、CyclicBarrier、CountDownLatch"吧!

(一)概述

资源的分配方式有两种,一种是独占,比如之前讲的ReentrantLock,另外一种是共享,即我们今天将要学习的SemaphoreCyclicBarrier以及CountDownLatch。这些都是JUC包中的类。

(二)Semaphore

Semaphore是信号量的意思,作用是控制访问特定资源的线程数量。 其核心API为:

semaphore.acquire();semaphore.release();

这么说可能比较模糊,下面我举个例子。

Semaphore就好比游乐园中的某个游乐设施的管理员,用来控制同时玩这个游乐设施的人数。比如跳楼机只能坐十个人,就设置Semaphore的permits等于10。

每当有一个人来时,首先判断permits是否大于0,如果大于0,就把一个许可证给这个人,同时自己的permits数量减一。

如果permits数量等于0了,其他人再想进来时就只能排队了。

当一个人玩好之后,这个人把许可证还给Semaphore,permits加1,正在排队的人再来竞争这一个许可证。

下面通过代码来演示这样一个场景

public class SemaphoreTest {    public static void main(String[] args) {        //创建permits等于2        Semaphore semaphore=new Semaphore(2);        //开五个线程去执行PlayGame        for (int i = 0; i < 5; i++) {            new Thread(new PlayGame(semaphore)).start();        }    }    static class PlayGame extends Thread{        Semaphore semaphore;        public PlayGame(Semaphore semaphore){            this.semaphore=semaphore;        }        @Override        public void run() {            try {                semaphore.acquire();                System.out.println(Thread.currentThread().getName()+"获得一个许可证");                Thread.sleep(1000);                System.out.println(Thread.currentThread().getName()+"释放一个许可证");                semaphore.release();            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}

在这里设置Semaphore的permit等于2,表示同时只有两个线程可以执行,然后开五个线程,在执行前通过semaphore.acquire();获取permit,执行后通过semaphore.release();归还permit。

通过结果可以观察到,每次最多只会有两个线程执行PlayGame 。

(三)Semaphore原理

3.1 默认非公平锁

Semaphore默认创建的是一个非公平锁:

3.2 Semaphore源码分析

Semaphore的实现方式和ReentrantLock十分类似。

首先定义一个内部类Sync继承AbstractQueuedSynchronizer

从Sync的构造方法中可以看到,初始化时设置state等于permits,在讲ReentrantLock的时候,state用来存储重入锁的次数,在Semaphore中state用来存储资源的数量。

Semaphore的核心方法是acquire和release,当执行acquire方法时,sync会执行一个获取一个共享资源的操作:

核心是判断剩余数量是否大于0,如果是的话就通过cas操作去获取资源,否则就进入队列中等待

当执行release方法时,sync会执行一个将一个共享资源放回去的cas操作

(四)CountDownLatch

countdownlatch能够让一个线程等待其他线程工作完成之后再执行。

countdownlatch通过一个计数器来实现,初始值是指定的数量,每当一个线程完成自己的任务后,计数器减一,当计数器为0时,执行最后的等待线程。

其核心API为

CountDownLatch.countDown();CountDownLatch.await();

下面来看代码示例:

设定countDownLatch初始值为2,定义两个线程分别执行对应的方法,方法执行完毕后再执行countDownLatch.countDown(); 这两个方法执行的过程中,主线程被countDownLatch.await();阻塞,只有等到其他线程都执行完毕之后才可执行。

public class CountDownLatchTest {        public static void main(String[] args) throws InterruptedException {        //设定初始值为2        CountDownLatch countDownLatch=new CountDownLatch(2);        //执行两个任务        new Thread(new Task1(countDownLatch)).start();        new Thread(new Task2(countDownLatch)).start();        //在两个任务执行完之后才会执行await方法之后的代码        countDownLatch.await();        System.out.println("其余两个线程执行完之后执行");    }    private static class Task1 implements Runnable {        private CountDownLatch countDownLatch;        public Task1(CountDownLatch countDownLatch) {            this.countDownLatch=countDownLatch;        }        @Override        public void run() {            System.out.println("执行任务一");            try {                Thread.sleep(2000);            } catch (InterruptedException e) {                e.printStackTrace();            }finally {                if (countDownLatch!=null){                    //执行完毕后调用countDown                    countDownLatch.countDown();                }            }        }    }    private static class Task2 implements Runnable {        private CountDownLatch countDownLatch;        public Task2(CountDownLatch countDownLatch) {            this.countDownLatch=countDownLatch;        }        @Override        public void run() {            System.out.println("执行任务二");            try {                Thread.sleep(3000);            } catch (InterruptedException e) {                e.printStackTrace();            }finally {                if (countDownLatch!=null){                    //执行完毕后调用countDown                    countDownLatch.countDown();                }            }        }    }}

效果如下:

(五)CyclicBarrier

栅栏屏障,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。 其核心API为:

cyclicBarrier.await();

和countdownlatch的区别在于,countdownlatch是一个线程等待其他线程执行完毕后再执行,CyclicBarrier是每一个线程等待所有线程执行完毕后,再执行。

看代码,初始化cyclicBarrier为3,两个子线程和一个主线程执行完时都会被阻塞在cyclicBarrier.await();代码前,等三个线程都执行完毕后再执行接下去的代码。

public class CyclicBarrierTest {    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {        CyclicBarrier cyclicBarrier=new CyclicBarrier(3);        System.out.println("执行主线程");        new Thread(new Task1(cyclicBarrier)).start();        new Thread(new Task2(cyclicBarrier)).start();        cyclicBarrier.await();        System.out.println("三个线程都执行完毕,继续执行主线程");    }    private static class Task1 implements Runnable {        private CyclicBarrier cyclicBarrier;        public Task1(CyclicBarrier cyclicBarrier) {            this.cyclicBarrier=cyclicBarrier;        }        @Override        public void run() {            System.out.println("执行任务一");            try {                Thread.sleep(2000);                cyclicBarrier.await();                System.out.println("三个线程都执行完毕,继续执行任务一");            } catch (InterruptedException e) {                e.printStackTrace();            } catch (BrokenBarrierException e) {                e.printStackTrace();            }        }    }    private static class Task2 implements Runnable {        private CyclicBarrier cyclicBarrier;        public Task2(CyclicBarrier cyclicBarrier) {            this.cyclicBarrier=cyclicBarrier;        }        @Override        public void run() {            System.out.println("执行任务二");            try {                Thread.sleep(2000);                cyclicBarrier.await();                System.out.println("三个线程都执行完毕,继续执行任务二");            } catch (InterruptedException e) {                e.printStackTrace();            } catch (BrokenBarrierException e) {                e.printStackTrace();            }        }    }}

结果如下:

cyclicBarrier还可以重复执行,而不需要重新去定义。

public static void main(String[] args) throws BrokenBarrierException, InterruptedException {    CyclicBarrier cyclicBarrier=new CyclicBarrier(3);    //第一次    System.out.println("执行主线程");    new Thread(new Task1(cyclicBarrier)).start();    new Thread(new Task2(cyclicBarrier)).start();    cyclicBarrier.await();    System.out.println("三个线程都执行完毕,继续执行主线程");    //第二次    System.out.println("执行主线程");    new Thread(new Task1(cyclicBarrier)).start();    new Thread(new Task2(cyclicBarrier)).start();    cyclicBarrier.await();}

感谢各位的阅读,以上就是"怎么使用JUC中的Semaphore、CyclicBarrier、CountDownLatch"的内容了,经过本文的学习后,相信大家对怎么使用JUC中的Semaphore、CyclicBarrier、CountDownLatch这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

0