Java并发编程第9讲——CountDownLatch、CyclicBarrier和Semaphore(万字详解)

在JDK的并发包(JUC)里提供了几个非常有用的并发工具类。CountDownLatch、CyclicBarrier和Samaphore工具类提供了一种并发流程控制的手段,这同样也是面试和工作中的一个重要知识点,本文将从它们的定义、常用方法、代码示例及核心源码的分析等几个要点详细介绍一下。

一、CountDownLatch(闭锁)

1.1 什么是CountDownLatch

CountDownLatch是一个计数器,它允许一个或多个线程等待其它线程完成操作后再继续执行,通常用来实现一个线程等待其它多个线程完成操作之后再继续执行的操作。

CountDownLatch内部维护了一个计数器,该计数器通过CountDownLatch的构造方法指定。当调用await()方法时,它将一直阻塞,直到计数器变为0。当其它线程执行完指定的任务后,可以调用countDown()方法将计数器减一。当计数器减为0,所有的线程将同时被唤醒,然后继续执行。

举个例子——王者5V5模式:当10个玩家都选完英雄后会进入加载界面,直到10个人都加载到100%,才会真正进入游戏。

1.2 常用方法

  • CountDownLatch(int count):CountDownLatch的构造方法,可通过count参数指定计数次数,但是要大于等于0,小于0会抛IIegalArgumentException异常。
  • void await()在计数等于0之前,会一直阻塞(在线程没被打断的情况下)。
  • boolean await(long timeout,TimeUnit unit)除非线程被中断,否则会一直阻塞,直至计数器减为0或超出指定时间timeout,当计数器为0返回true,当超过指定时间,返回false。
  • void countDown()调用一次,计数器就减1,当等于0时,释放所有线程。如果计数器的初始值就是0,那么就当没有用CountDownLatch吧。
  • long getCount()返回当前计数器的数量,可以用来测试和调试。

1.3 举个例子

首先我们创建一个Worker类,并使用CountDownLatch来控制它执行多少次后放行,output变量充当运行日志来记录运行轨迹:

@AllArgsConstructor
@NoArgsConstructor
public class Worker implements Runnable{
    private List<String> output;
    private CountDownLatch countDownLatch;
    @Override
    public void run() {
        //do something
        countDownLatch.countDown();
        output.add("CountDown减一,剩余:"+countDownLatch.getCount());
    }
}

接下来测试一下:

public class TestCountDownLatch {
​
    public static void main(String[] args) throws InterruptedException {
        List<String> output = Collections.synchronizedList(new ArrayList<>());
        CountDownLatch countDownLatch=new CountDownLatch(5);
​
        List<Thread> workers = Stream.generate(() -> new Thread(new Worker(output, countDownLatch)))
                .limit(5)
                .collect(Collectors.toList());
​
        workers.forEach(Thread::start);
        //阻塞
        countDownLatch.await();
        output.add("计数器减为"+countDownLatch.getCount()+",放行了!!!");
        for (String s : output) {
            System.out.println(s);
        }
    }
}

执行结果:

CountDown减一,剩余:4
CountDown减一,剩余:3
CountDown减一,剩余:2
CountDown减一,剩余:1
CountDown减一,剩余:0
计数器减为0,放行了!!!

很显然,“计数器减为0,放行了!!!”这句话总是最后一个输出,因为它依赖于CountDownLatch的释放。

1.4 核心源码

CountDownLatch的实现原理主要时通过内部类Sync类实现的,而内部类Sync是AQS的子类,通过重写AQS的共享式获取和释放同步状态方法实现的。如下:

ps:下面逐个介绍CountDownLatch的核心方法时会解释。

1.4.1 CountDownLatch(int count)

先来看一下CountDownLatch的构造方法,对应Sync类的第一个红框。

解释:初始化CountDownLatch实际就是设置了AQS的state为计数的值。

1.4.2 long getCount()

对应Sync类的第二个红框。

解释:获取AQS的state值。

1.4.3 void await()

对应Sync类的第三个红框。

解释:调用await方法实际就是调用AQS的共享式获取同步状态的方法,acquireSharedInterruptibly方法里调用了Sync类的tryAcquireShared方法,该方法返回值小于0代表计数器还未减为0,那么继续调用doAcquireSharedInterruptibly方法。

里面时一个死循环for,跳出死循环的条件就是Sync类的tryAcquireShared方法返回值大于等于0,也就是计数器减为0了,否则将一直处在死循环中,也就是会一直阻塞。

1.4.4 void countDown()

对应Sync类的第四个红框。

解释:调用CountDownLatch的countDown方法实际就是调用AQS的释放同步状态的方法,每调用一次就自减一次state的值。

1.5 小结

CountDownLatch实际完全依靠AQS的共享式获取和释放同步状态来实现,初始化时定义AQS的state值,每调用countDown实际就是释放一次AQS的共享式同步状态,await方法实际就是尝试获取AQS的同步状态,只有当同步状态值为0时才能获取成功。

二、CyclicBarrier(栅栏)

2.1 什么是CyclicBarrier

CyclicBarrier是一个同步屏障,它允许多个线程相互等待,直到到达某个公共屏障点,才能继续执行。通常用来实现多个线程在同一个屏障处等待,然后再一起继续执行的操作。

CyclicBarrier也维护了一个类似计数器的变量,通过CyclicBarrier的构造函数指定,需要大于0,否则抛IllegalArgumenException异常。当线程到达屏障位置时,调用await()方法进行阻塞,直到所有线程到达屏障位置时,所有线程才会被释放,而屏障将会被重置为初始值以便下次使用。

举个例子——王者五排:五个人都到齐后,才能开始匹配。

2.2 常用方法

  • CyclicBarrier(int parties)CyclicBarrier的构造方法,可通过parties参数指定需要到达屏障的线程个数,但是要大于0,否则会抛IllegalArgumentException异常。
  • CyclicBarrier(int parties,Runnable barrierAction)另一个构造方法,parties作用同上,barrierAction表示最后一个到达屏障点的线程要执行的逻辑。
  • int await()表示线程到达屏障点,并等待其它线程到达,返回值表示当前线程在屏障中的位置(第几个到达的)。
  • int await(long timeout,TimeUnit unit)与await()类似,但是设置了超时时间,如果超过指定的时间后,仍然还有线程没有到达屏障点,则等待的线程会被唤醒并执行后续操作。
  • void reset()重置屏障状态,即将屏障计数器重置为初始值。
  • int getParties()获取需要同步的线程数量。
  • int getNumberWaiting()获取当前正在等待的线程数量。

2.3 举个例子

假如有一个由固定数量线程执行的操作,并将相应的结果存到一个列表中。当所有线程完成它们的操作时,其中一个线程开始处理每个线程获取到的数据。

@AllArgsConstructor
@NoArgsConstructor
public class CyclicBarrierDemo {
    private CyclicBarrier cyclicBarrier;
    //存储每个工作线程的结果
    private List<Integer> results = Collections.synchronizedList(new ArrayList<>());
    private Random random = new Random();
    //每个工作线程要产生的结果数量
    private int resultNums;
    //要执行的线程数量
    private int workerNums;
​
    //每个线程的执行逻辑
    class AcquireNums implements Runnable {
        @Override
        public void run() {
            String thisThreadName = Thread.currentThread().getName();
​
            //Random产生随机数并存入
            for (int i = 0; i < resultNums; i++) {
                Integer num = random.nextInt(10);
                System.out.println(thisThreadName + ": 随机结果 - " + num);
                results.add(num);
            }
​
            try {
                System.out.println(thisThreadName + " 等待其它线程到达屏障点.");
                //阻塞其它线程
                cyclicBarrier.await();
            } catch (Exception e) {
​
            }
        }
    }
​
    //所有线程到达屏障点时执行的操作
    class AfterThreadWork implements Runnable {
        @Override
        public void run() {
            String thisThreadName = Thread.currentThread().getName();
            int sum = 0;
            //计算结果
            for (Integer result : results) {
                System.out.println("加"+result);
                sum += result;
            }
            System.out.println(thisThreadName + ": 总和 = " + sum);
        }
    }
​
    //程序入口
    public void runSimulation(int numWorkers, int numberOfresults) {
        resultNums = numberOfresults;
        workerNums = numWorkers;
        cyclicBarrier = new CyclicBarrier(workerNums, new AfterThreadWork());
        System.out.println("创建" + workerNums + "个线程,每个线程获取" + resultNums + "个结果");
        for (int i = 0; i < workerNums; i++) {
            Thread worker = new Thread(new AcquireNums());
            worker.setName("Thread " + i);
            worker.start();
        }
    }
​
    public static void main(String[] args) {
        CyclicBarrierDemo demo = new CyclicBarrierDemo();
        demo.runSimulation(5, 3);
    }
}

上述代码使用5个线程初始化了CyclicBarrier,每个线程都会产生3个随机整数,并将其存储在结果集合里,当所有的线程都到达屏障点时,最后一个到达的将会执行AfterThreadWork中的逻辑,即计算集合里数字的总和。

执行结果:

创建5个线程,每个线程获取3个结果
Thread 0: 随机结果 - 7
Thread 0: 随机结果 - 0
Thread 0: 随机结果 - 8
Thread 0 等待其它线程到达屏障点.
Thread 1: 随机结果 - 1
Thread 3: 随机结果 - 5
Thread 2: 随机结果 - 8
Thread 2: 随机结果 - 6
Thread 3: 随机结果 - 5
Thread 4: 随机结果 - 5
Thread 1: 随机结果 - 1
Thread 4: 随机结果 - 4
Thread 3: 随机结果 - 0
Thread 3 等待其它线程到达屏障点.
Thread 2: 随机结果 - 4
Thread 2 等待其它线程到达屏障点.
Thread 4: 随机结果 - 8
Thread 4 等待其它线程到达屏障点.
Thread 1: 随机结果 - 0
Thread 1 等待其它线程到达屏障点.
Thread 1: 总和 = 62

2.4 核心源码

2.4.1 构造方法

主要有两个属性:parties(总线程数)、count(当前剩余线程数),需要两个值的维护的原因是CyclicBarrier提供了重置的功能,当调用reset方法时,需要将count的值再次重置为parties的初始值。

2.4.2 breakBarrier()

ps:下面多处会提到。

解释:用于中断屏障操作。

  • 将broken置为true。
  • count的值置为parties初始值。
  • 调用Condition的singnalAll方法唤醒所有线程。

2.4.3 nextGeneration()

ps:下面多处会提到。

解释:作用是完成上一代的屏障操作,为下一代屏障操作做准备。

  • 调用Condition的signalAll方法唤醒所有线程。
  • count的值置为parties初始值。
  • 创建新的Generation实例,作为下一代表示。

2.4.4 await()

可以看到await方法里面调用了dowait方法,下面我们看一下dowait方法(dowait的方法有点长,分两次分析)  

解释:

  • 第一个红框:用ReentrantLock加锁,防止出现并发问题。

  • 第二个红框:

    • Generation是CyclicBarrier的一个内部类,里面仅有一个boolean类型的broken属性(初始值为false)。此处的作用是检查broken是否为ture,若为true,则抛异常,这就意味着当前屏障已经被中断或重置了,避免了继续等待。

    • 第一个if:判断屏障是否失效,若失效,抛异常。

    • 第二个if:如果线程被中断,那么调用breakBarrier方法直接中断屏障,并抛出异常。

  • 第三个红框:

    • 计数器减一。

    • 判断所有线程是否都到达屏障。

    • 如果index==0,则执行屏障操作。如果有需要优先执行的任务(CyclicBarrier构造方法的第二个参数是否为空),则执行run()方法。调用nextGeneration方法为下一代做准备。

    • finally:如果不能执行屏障操作(ranAction为false),则调用breakBarrier方法中止屏障操作。

接着第二段源码:

解释:如果计数器没减到0,就让当前线程进入到等待队列中等待

  • 第一个红框:

    • timed=ture表示设置了超时等待时间,timed=false表示没设置。没设置的话就调用Condition的await()方法进入到等待队列。nacos表示超时时间,大于0表示设置了超时时间,那么就会调用Condition的awaitNanos方法在限定的时间内等待。

    • 如果等待过程中发生了中断interruptedException,进入catch逻辑:如果g是当前Generation并且broken为false,则调用breakBarrier方法终止屏障操作,并抛出异常。反之,说明Genertion已经不是最新的

  • 第二个红框:

    • 第1个if:broken为ture,表示屏障已被中断或重置,抛出异常。

    • 第2个if:如果g!=Generation,说明当前Generation不是最新的了,则返回当前线程在屏障上等待的位置index。

    • 第3个if:如果设置了超时等待且已经超时,则调用breakBarrier方法终止屏障操作,并抛出异常。

2.4.5 reset()

解释:重置屏障,将其恢复至初始状态

  • 加锁。
  • 获取锁后调用breakBarrier方法终止屏障操作。
  • 接着调用nextGeneration方法为下一代屏障操作做准备。

2.5 小结

从源码可以看出CyclicBarrier的实现原理主要是通过ReentranLock和Condition来实现的,主要的流程如下:

  • 创建CyclicBarrier时指定需要到达屏障的线程数parties。
  • 当调用await方法时,会首先通过ReentranLock进行加锁,然后对count进行自减操作。
  • 如果计数器没减为0,则调用Condition的await或awaitNanos方法使当前线程进入等待状态。
  • 如果计数器减为0了,表示全部抵达屏障,此时就调用nextGeneration方法为下一代屏障操作做准备。

思考:既然CycliBarrier可以用ReentranLock+Condition实现,那么Synchronized+wait/notify是否也可以实现CyclicBarrier?

ps:关于锁,可以参考另外一篇文章:Java并发编程第4讲——Java中的锁(万字详解)

三、CountDownLatch vs CyclicBarrier

趁热打铁,介绍一下它俩的区别,最后再介绍Samphore(信号量)。

3.1 功能

  • CountDownLatch:主要作用于一个或多个线程等待其它线程完成一组操作,计数器为0时,会唤醒所有的等待线程。
  • CyclicBarrier:主要用于一组线程互相等待,直到所有线程都到达屏障后才继续执行,之后CyclicBarrier可以被重用。

3.2 任务vs线程

根据上述功能,我们可以知道CyclicBarrier允许一组线程互相等待,而CountDownLatch允许一个或多个线程等待一些任务完成。

说白了,就是CyclicBarrier维护线程的计数,而CounDownLatch维护任务的计数。

上代码:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch=new CountDownLatch(2);
        new Thread(()->{
            countDownLatch.countDown();
            countDownLatch.countDown();
        }).start();
        countDownLatch.await();
        System.out.println(countDownLatch.getCount());//0
    }
}

注意:我们用了一个线程执行了两次countDown操作,结果计数器为0。我们再来看看CyclicBarrier:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        CyclicBarrier cyclicBarrier=new CyclicBarrier(2);
        new Thread(()->{
            try {
                cyclicBarrier.await();
                cyclicBarrier.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                throw new RuntimeException(e);
            }
        }).start();
        Thread.sleep(100);
        //当前正在屏障点等待的线程
        System.out.println(cyclicBarrier.getNumberWaiting());//1
        System.out.println(cyclicBarrier.isBroken());//false
    }
}

可以看到,单个线程无法两次减少屏障的计数,第二个await是无用的,还有就是broken为false,说明还在本Generation中。

3.3 可重用性

CountDownLatch和CyclicBarrier最明显的差异就是可重用性。CyclicBarrier所有线程都到达屏障后,计数会重置为初始值。而CountDownLatch永远不会重置。

上代码:定义一个计数器为5的CountDownLatch,通过10次不同的调用计数

public class TestNums {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch=new CountDownLatch(5);
        List<String> list=new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                //当前计数器的数量
                long count = countDownLatch.getCount();
                countDownLatch.countDown();
                if(countDownLatch.getCount() != count){
                    list.add("计数器次数变了");
                }
            }).start();
        }
        Thread.sleep(100);
        System.out.println(list.size());//5
    }
}

上述代码,可以看出,每次一个线程运行时,值都会减少,一旦等于0,计数器也不会重置。

再来看看CyclicBarrier:

public class TestNums {
    public static void main(String[] args) throws InterruptedException {
        CyclicBarrier cyclicBarrier=new CyclicBarrier(5,()->{
            System.out.println("所有线程都到达屏障!!!");
        });
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    throw new RuntimeException(e);
                }
            }).start();
        }
    }
}

输出:

所有线程都到达屏障!!!
所有线程都到达屏障!!!

可以看到所有线程到达屏障后,需要优先执行的逻辑执行了两次,说明,完成了两次所有线程到达屏障的操作,也就是说CycliBarrier的计数器一旦为0,便会将计数器重置为初始值。

四、Semaphore(信号量)

4.1 什么是Semaphore

Semaphore是一个计数信号量,它允许多个线程同时访问共享资源,并通过计数器来控制访问数量。它通常用来实现一个线程需要等待获取一个许可证才能访问共享资源,或者需要释放一个许可证才能完成的操作。

Semaphore维护了一个内部计数器(许可permits),主要有两个操作,分别对应Semaphore的acquire和release方法。acquire方法用于获取资源,当计数器大于0时,将计数器减1;当计数器等于0时,将线程阻塞。release方法用于释放资源,将计数器加1,并唤醒一个等待中的线程。

举个例子:假设停车场有5个车位,如果同时来了6辆车,那么保安只允许5辆车进入(获取许可),剩下的一辆车只能等待它们其中一辆开走(释放许可),才能进入。

4.2 常用方法

  • Semaphore(int permits)构造方法,permits表示Semaphore中的许可数量,它决定了同时可以访问某个资源的线程数量。
  • Semaphore(int permits,boolean fair)构造方法,当fair为ture,设置为公平信号量。
  • void acquire()获取一个许可,如果没有许可,则当前线程被阻塞,直到有许可。如果有许可该方法会将许可数量减1。
  • void acquire(int permits)获取指定数量的许可,获取成功同样将许可减去指定数量,失败阻塞。
  • void release()释放一个许可,将许可数加1。如果有其他线程正在等待许可,则唤醒其中一个线程。
  • void release(int n)释放n个许可。
  • int availablePermits()当前可用许可数。

4.3 举个例子

假设停车厂有固定的停车车位,每次停车和离开都会占有或释放一个车位:

public class SemaphoreTest {
    private Semaphore parking;
    //初始化许可数量
    public SemaphoreTest(int n){
        parking=new Semaphore(n);
    }
    //停车
    public void park(){
        try {
            //获取一个停车位,如果没有则阻塞
            parking.acquire();
            System.out.println(Thread.currentThread().getName()+" 停车成功!");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    //释放车位
    public void leave(){
        //释放一个车位
        parking.release();
        System.out.println(Thread.currentThread().getName()+" 离开了停车场!");
    }
    public static void main(String[] args) {
        //初始化3个停车位
        SemaphoreTest parking=new SemaphoreTest(3);
        //假设此时有5辆车需要停放
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                //停车
                parking.park();
                try {
                    //假设每辆车停留300毫秒
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //离开停车厂
                parking.leave();
            },"Car-"+i).start();
        }
    }
}

上述代码难度不大,就不做解释了,来看下结果吧:

Car-0 停车成功!
Car-1 停车成功!
Car-2 停车成功!
Car-1 离开了停车场!
Car-4 停车成功!
Car-0 离开了停车场!
Car-2 离开了停车场!
Car-3 停车成功!
Car-3 离开了停车场!
Car-4 离开了停车场!

4.4 核心源码

4.4.1 构造方法

解释:

  • 构造方法有两个参数,permits是许可证的数量,fair表示是否开启公平模式,模式是非公平模式。

Semaphore的实现同样也是通过其内部类Sync类来实现的,Sync类也是AQS的子类。读到这不知道你想起ReentrantLock了没,没错,Semaphore的实现原理基本上和ReentrantLock如出一辙。

4.4.2 acquire()公平模式

遵循FIFO原则,先排队的线程先拿到许可证。

可以看到acquire方法调用了acquireSharedInterruptible方法,而acquireSharedInterruptible方法里有一个tryAcquireShared方法的if判断,这里先说一下,tryAcquireShared方法的返回值小于0表示获取许可失败,失败的情况下会调用doAcquireSharedInterrutpible方法对线程进行阻塞,下面我们先看一下tryAcquireShared方法的源码。

解释:

  • 检查是否有其他线程在当前线程之前排队等待,如果有,则意味着当前线程无法获取资源,直接返回-1。
  • 计算剩余资源数量(remaining),即当前资源数量-需要获取的资源数量。接着判断remaining是否小于0或使用compareAndSetState方法尝试将当前资源状态更新位剩余资源数量。如果小于0或cas成功,返回剩余资源数量;反之则不断循环重试。

ps:remain小于0说明当前许可不够,因此需要立即返回(这个值是负数),这是一种快速失败的策略。到了cas的判断的时候就说明许可数量足够,只需要判断更新是否成功。

最后看一下doAcquireSharedInterrutpible方法

可以看到这又是一个死循环,里面又调了tryAcquireShared方法不断尝试获取许可,只有成功或许许可(返回值大于0)才跳出循环,也就是解除阻塞。

4.4.3 acquire()非公平模式

非公平就谁拿到资源谁用,不分先后。

只是少了一步检查是否有其他线程在当前线程之前排队等待的判断而已,剩余的也都一样,这里就不做过多的介绍了。

4.4.4 release()

release方法调用了父类AQS的releaseShared方法,releaseShared方法里,调用Sync类的tryReleaseShared方法做了条件判断,如果为ture就调用AQS父类的doReleaseShared方法,接着返回ture表示释放许可成功,反之返回false表示释放许可失败。下面我们来看下这两个方法的源码。

tryReleaseShared():

解释:

  • 当前许可数量+需要释放的许可数量得到释放后许可的数量next。
  • 如果next小于当前许可的数量,说明许可数发生了溢出,就抛异常。
  • 如果next大于等于当前许可的数量,尝试将当前许可数更新为next,成功返回ture,如果都不满足就继续循环,直到尝试成功。

如果释放成功,则调用AQS父类的doReleaseShared方法:

解释:简单的说就是更新许可数量成功后,就会唤醒等待队列中的其它线程去竞争来获取许可。(此处是AQS的源码,这里就简单解释一下,后续会总结AQS的文章)

4.5 小结

Semaphore主要用于控制当前活动线程数目,就像上面停车场的例子,Semaphore就相当于停车场看管员,主要的职责就是控制车辆和车位,而对于每辆车来说就如同一个线程,线程需要通过acquire()方法获取许可,而release()方法释放许可。如果许可数达到最大活动数,那么调用acquire()之后,便进入等待队列,等待已获得许可的线程释放许可,从而使得多线程能够合理的运行。

End:希望对大家有所帮助,如果有纰漏或者更好的想法,请您一定不要吝啬你的赐教🙋。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
线程的状态以及各状态之间的转换详解.mp4 线程的初始化,中断以及其源码解.mp4 多种创建线程的方式案例演示(一)带返回值的方式.mp4 多种创建线程的方式案例演示(二)使用线程池.mp4 Spring对并发的支持:Spring的异步任务.mp4 使用jdk8提供的lambda进行并行计算.mp4 了解多线程所带来的安全风险.mp4 从线程的优先级看饥饿问题.mp4 从Java字节码的角度看线程安全性问题.mp4 synchronized保证线程安全的原理(理论层面).mp4 synchronized保证线程安全的原理(jvm层面).mp4 单例问题与线程安全性深入解析.mp4 理解自旋锁,死锁与重入锁.mp4 深入理解volatile原理与使用.mp4 JDK5提供的原子类的操作以及实现原理.mp4 Lock接口认识与使用.mp4 手动实现一个可重入锁.mp4 AbstractQueuedSynchronizer(AQS)详解.mp4 使用AQS重写自己的锁.mp4 重入锁原理与演示.mp4 读写锁认识与原理.mp4 细读ReentrantReadWriteLock源码.mp4 ReentrantReadWriteLock锁降级详解.mp4 线程安全性问题简单总结.mp4 线程之间的通信之wait notify.mp4 通过生产者消费者模型理解等待唤醒机制.mp4 Condition的使用及原理解析.mp4 使用Condition重写waitnotify案例并实现一个有界队列.mp4 深入解析Condition源码.mp4 实战:简易数据连接池.mp4 线程之间通信之join应用与实现原理剖析.mp4 ThreadLocal 使用及实现原理.mp4 并发工具类CountDownLatch详解.mp4 并发工具类CyclicBarrier 详解.mp4 并发工具类Semaphore详解.mp4 并发工具类Exchanger详解.mp4 CountDownLatch,CyclicBarrier,Semaphore源码解析.mp4 提前完成任务之FutureTask使用.mp4 Future设计模式实现(实现类似于JDK提供的Future).mp4 Future源码解读.mp4 ForkJoin框架详解.mp4 同步容器与并发容器.mp4 并发容器CopyOnWriteArrayList原理与使用.mp4 并发容器ConcurrentLinkedQueue原理与使用.mp4 Java中的阻塞队列原理与使用.mp4 实战:简单实现消息队列.mp4 并发容器ConcurrentHashMap原理与使用.mp4 线程池的原理与使用.mp4 Executor框架详解.mp4 实战:简易web服务器(一).mp4 实战:简易web服务器(二).mp4 JDK8的新增原子操作类LongAddr原理与使用.mp4 JDK8新增锁StampedLock详解.mp4 重排序问题.mp4 happens-before简单概述.mp4 锁的内存语义.mp4 volatile内存语义.mp4 final域的内存语义.mp4 实战:问题定位.mp4
龙果 java并发编程原理实战 第2节理解多线程与并发的之间的联系与区别 [免费观看] 00:11:59分钟 | 第3节解析多线程与多进程的联系以及上下文切换所导致资源浪费问题 [免费观看] 00:13:03分钟 | 第4节学习并发的四个阶段并推荐学习并发的资料 [免费观看] 00:09:13分钟 | 第5节线程的状态以及各状态之间的转换详解00:21:56分钟 | 第6节线程的初始化,中断以及其源码解00:21:26分钟 | 第7节多种创建线程的方式案例演示(一)带返回值的方式00:17:12分钟 | 第8节多种创建线程的方式案例演示(二)使用线程池00:15:40分钟 | 第9节Spring对并发的支持:Spring的异步任务00:11:10分钟 | 第10节使用jdk8提供的lambda进行并行计算00:14:22分钟 | 第11节了解多线程所带来的安全风险00:13:16分钟 | 第12节从线程的优先级看饥饿问题00:18:42分钟 | 第13节从Java字节码的角度看线程安全性问题00:25:43分钟 | 第14节synchronized保证线程安全的原理(理论层面)00:13:59分钟 | 第15节synchronized保证线程安全的原理(jvm层面)00:25:03分钟 | 第16节单例问题与线程安全性深入解析00:27:15分钟 | 第17节理解自旋锁,死锁与重入锁00:24:58分钟 | 第18节深入理解volatile原理与使用00:28:30分钟 | 第19节JDK5提供的原子类的操作以及实现原理00:27:10分钟 | 第20节Lock接口认识与使用00:19:54分钟 | 第21节手动实现一个可重入锁00:26:31分钟 | 第22节AbstractQueuedSynchronizer(AQS)详解00:49:04分钟 | 第23节使用AQS重写自己的锁00:31:04分钟 | 第24节重入锁原理与演示00:12:24分钟 | 第25节读写锁认识与原理00:18:04分钟 | 第26节细读ReentrantReadWriteLock源码00:30:38分钟 | 第27节ReentrantReadWriteLock锁降级详解00:13:32分钟 | 第28节线程安全性问题简单总结00:15:34分钟 | 第29节线程之间的通信之wait/notify00:32:12分钟 | 第30节通过生产者消费者模型理解等待唤醒机制00:20:50分钟 | 第31节Condition的使用及原理解析00:17:40分钟 | 第32节使用Condition重写wait/notify案例并实现一个有界队列00:22:05分钟 | 第33节深入解析Condition源码00:21:15分钟 | 第34节实战:简易数据连接池00:24:53分钟 | 第35节线程之间通信之join应用与实现原理剖析00:10:17分钟 | 第36节ThreadLocal 使用及实现原理00:17:41分钟 | 第37节并发工具类CountDownLatch详解00:22:04分钟 | 第38节并发工具类CyclicBarrier 详解00:11:52分钟 | 第39节并发工具类Semaphore详解00:17:27分钟 | 第40节并发工具类Exchanger详解00:13:47分钟 | 第41节CountDownLatch,CyclicBarrier,Semaphore源码解析00:29:57分钟 | 第42节提前完成任务之FutureTask使用00:11:43分钟 | 第43节Future设计模式实现(实现类似于JDK提供的Future)00:19:20分钟 | 第44节Future源码解读00:29:22分钟 | 第45节Fork/Join框架详解00:28:09分钟 | 第46节同步容器与并发容器00:18:44分钟 | 第47节并发容器CopyOnWriteArrayList原理与使用00:15:52分钟 | 第48节并发容器ConcurrentLinkedQueue原理与使用00:31:03分钟 | 第49节Java中的阻塞队列原理与使用00:26:18分钟 | 第50节实战:简单实现消息队列00:11:07分钟 | 第51节并发容器ConcurrentHashMap原理与使用00:38:22分钟 | 第52节线程池的原理与使用00:42:49分钟 | 第53节Executor框架详解00:36:54分钟 | 第54节实战:简易web服务器(一)00:55:34分钟 | 第55节实战:简易web服务器(二)00:24:36分钟 | 第56节JDK8的新增原子操作类LongAddr原理与使用00:17:45分钟 | 第57节JDK8新增锁StampedLock详解00:29:37分钟 | 第58节重排序问题00:23:19分钟 | 第59节happens-before简单概述00:15:17分钟 | 第60节锁的内存语义00:13:54分钟 | 第61节volatile内存语义00:12:04分钟 | 第62节final域的内存语义00:34:07分钟 | 第63节实战:问题定位00:07:48分钟
第1节你真的了解并发吗? [免费观看][免费观看] 00:27:48分钟 | 第2节理解多线程与并发的之间的联系与区别 [免费观看] 00:11:59分钟 | 第3节解析多线程与多进程的联系以及上下文切换所导致资源浪费问题 [免费观看] 00:13:03分钟 | 第4节学习并发的四个阶段并推荐学习并发的资料 [免费观看] 00:09:13分钟 | 第5节线程的状态以及各状态之间的转换详解00:21:56分钟 | 第6节线程的初始化,中断以及其源码解00:21:26分钟 | 第7节多种创建线程的方式案例演示(一)带返回值的方式00:17:12分钟 | 第8节多种创建线程的方式案例演示(二)使用线程池00:15:40分钟 | 第9节Spring对并发的支持:Spring的异步任务00:11:10分钟 | 第10节使用jdk8提供的lambda进行并行计算00:14:22分钟 | 第11节了解多线程所带来的安全风险00:13:16分钟 | 第12节从线程的优先级看饥饿问题00:18:42分钟 | 第13节从Java字节码的角度看线程安全性问题00:25:43分钟 | 第14节sy nchronized保证线程安全的原理(理论层面)00:13:59分钟 | 第15节synchronized保证线程安全的原理(jvm层面)00:25:03分钟 | 第16节单例问题与线程安全性深入解析00:27:15分钟 | 第17节理解自旋锁,死锁与重入锁00:24:58分钟 | 第18节深入理解volatile原理与使用00:28:30分钟 | 第19节JDK5提供的原子类的操作以及实现原理00:27:10分钟 | 第20节Lock接口认识与使用00:19:54分钟 | 第21节手动实现一个可重入锁00:26:31分钟 | 第22节AbstractQueuedSynchronizer(AQS)详解00:49:04分钟 | 第23节使用AQS重写自己的锁00:31:04分钟 | 第24节重入锁原理与演示00:12:24分钟 | 第25节读写锁认识与原理00:18:04分钟 | 第26节细读ReentrantReadWriteLock源码00:30:38分钟 | 第27节ReentrantReadWriteLock锁降级详解00:13:32分钟 | 第28节线程安全性问题简单总结00:15:34分钟 | 第29节线程之间的通信之wait/notify00:32:12分钟 | 第30节通过生产者消费者模型理解等待唤醒机制00:20:50分钟 | 第31节Condition的使用及原理解析00:17:40分钟 | 第32节使用Condition重写wait/notify案例并实现一个有界队列00:22:05分钟 | 第33节深入解析Condition源码00:21:15分钟 | 第34节实战:简易数据连接池00:24:53分钟 | 第35节线程之间通信之join应用与实现原理剖析00:10:17分钟 | 第36节ThreadLocal 使用及实现原理00:17:41分钟 | 第37节并发工具类CountDownLatch详解00:22:04分钟 | 第38节并发工具类CyclicBarrier 详解00:11:52分钟 | 第39节并发工具类Semaphore详解00:17:27分钟 | 第40节并发工具类Exchanger详解00:13:47分钟 | 第41节CountDownLatch,CyclicBarrier,Semaphore源码解析00:29:57分钟 | 第42节提前完成任务之FutureTask使用00:11:43分钟 | 第43节Future设计模式实现(实现类似于JDK提供的Future)00:19:20分钟 | 第44节Future源码解读00:29:22分钟 | 第45节Fork/Join框架详解00:28:09分钟 | 第46节同步容器与并发容器00:18:44分钟 | 第47节并发容器CopyOnWriteArrayList原理与使用00:15:52分钟 | 第48节并发容器ConcurrentLinkedQueue原理与使用00:31:03分钟 | 第49节Java中的阻塞队列原理与使用00:26:18分钟 | 第50节实战:简单实现消息队列00:11:07分钟 | 第51节并发容器ConcurrentHashMap原理与使用00:38:22分钟 | 第52节线程池的原理与使用00:42:49分钟 | 第53节Executor框架详解00:36:54分钟 | 第54节实战:简易web服务器(一)00:55:34分钟 | 第55节实战:简易web服务器(二)00:24:36分钟 | 第56节JDK8的新增原子操作类LongAddr原理与使用00:17:45分钟 | 第57节JDK8新增锁StampedLock详解00:29:37分钟 | 第58节重排序问题00:23:19分钟 | 第59节happens-before简单概述00:15:17分钟 | 第60节锁的内存语义00:13:54分钟 | 第61节volatile内存语义00:12:04分钟 | 第62节final域的内存语义00:34:07分钟 | 第63节实战:问题定位00:07:48分钟 |
CountDownLatchCyclicBarrierSemaphore都是Java中用于多线程编程的同步工具类,但它们有不同的作用和使用场景。 1. CountDownLatch CountDownLatch(倒计时门闩)是一个同步工具类,它允许一个或多个线程等待其他线程完成操作后再继续执行。CountDownLatch维护了一个计数器,初始值为线程数量,线程完成任务后计数器减1。当计数器为0时,等待线程继续执行。CountDownLatch的主要方法是await()和countDown()。 使用场景:CountDownLatch适用于一组线程等待另一组线程完成操作后再继续执行的场景。比如,主线程等待所有子线程完成初始化后再继续执行。 2. CyclicBarrier CyclicBarrier(循环屏障)是一个同步工具类,它允许一组线程相互等待,直到所有线程都到达某个屏障点后再继续执行。CyclicBarrier的主要方法是await()。 使用场景:CyclicBarrier适用于需要多个线程协同完成某个任务的场景。比如,多个线程同时执行某个操作,需要等待所有线程都执行完后再进行下一步操作。 3. Semaphore Semaphore(信号量)是一个同步工具类,它允许多个线程同时访问某个资源。Semaphore维护了一个许可证数量,线程需要先获取许可证才能访问资源,访问完后释放许可证。Semaphore的主要方法是acquire()和release()。 使用场景:Semaphore适用于需要限制线程数量访问某个资源的场景。比如,数据库连接池限制同时访问连接的线程数量。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橡 皮 人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值