Entrevistador: Fale-me sobre os princípios de CountDownLatch, CyclicBarrier e Semaphore?

CountDownLatch

CountDownLatch é adequado para cenários multithread em que você precisa esperar que todos os sub-threads sejam executados antes de realizar as operações.

Por exemplo, uma reunião de departamento de manhã e alguém no banheiro. Nesse momento, você precisa esperar que todos voltem do banheiro antes de a reunião começar.

classe pública CountDownLatchTest { 
   private static int num = 3;
   private static CountDownLatch countDownLatch = new CountDownLatch (num);
   private static ExecutorService executorService = Executors.newFixedThreadPool (num);
   public static void main (String [] args) lança Exceção {
       executorService.submit (() -> {
           System.out.println ("A 在 上 厕所");
           tente {
               Thread.sleep (4000);
           } catch (InterruptedException e ) {
               e.printStackTrace ();
           } finalmente {
               countDownLatch.countDown ();
               System.out.println ("A 上 完了");
           }
       });
       executorService.submit (() -> {
           System.out.println ("B 在 上 厕所");
           tente {
               Thread.sleep (2000);
           } catch (InterruptedException e) {
               e.printStackTrace ();
           } finalmente {
               countDownLatch. countDown ();
               System.out.println ("B 上 完了");
           }
       });
       executorService.submit (() -> {
           System.out.println ("C 在 上 厕所");
           tente {
               Thread.sleep (3000);
           } catch (InterruptedException e) {
               e.printStackTrace ();
           } finalmente {
               countDownLatch.countDown ();
               System.out.println ("C está concluído");
           }
       });

       System.out.println ("Esperando que todos voltem do banheiro para uma reunião ...");
       countDownLatch.await ();
       Sistema .out.println ("Todos estão bem, comece a reunião ...");
       executorService.shutdown ();

   }}

Resultado da execução do código:

A está no banheiro 
B está no banheiro
esperando que todos voltem do banheiro para uma reunião ... C está no banheiro
B terminou
C terminou
A está acabado,
todos estão bem, e a reunião começa ...

Inicialize uma instância CountDownLatch e passe o parâmetro 3, porque temos 3 threads filho, cada vez que o thread filho é executado, o método countDown () é chamado para o contador -1. Depois que o thread principal chama o método await (), ele será bloqueado até que o contador se torne 0. , O método await () retorna e a execução é concluída. A diferença entre ele e o método join () é que join bloqueará o thread filho até o final da execução, enquanto CountDownLatch pode permitir o retorno de await () a qualquer momento e join não pode ser usado com ExecutorService. Em comparação, CountDownLatch é mais flexível.

CountDownLatch é implementado com base em AQS. O estado da variável volátil mantém um estado de contagem regressiva e as variáveis ​​compartilhadas multithread são visíveis.

  1. CountDownLatch通过构造函数初始化传入参数实际为AQS的state变量赋值,维持计数器倒数状态

  2. 当主线程调用await()方法时,当前线程会被阻塞,当state不为0时进入AQS阻塞队列等待。

  3. 其他线程调用countDown()时,state值原子性递减,当state值为0的时候,唤醒所有调用await()方法阻塞的线程

CyclicBarrier

CyclicBarrier叫做回环屏障,它的作用是让一组线程全部达到一个状态之后再全部同时执行,而且他有一个特点就是所有线程执行完毕之后是可以重用的。

public class CyclicBarrierTest {
   private static int num = 3;
   private static CyclicBarrier cyclicBarrier = new CyclicBarrier(num, () -> {
       System.out.println("所有人都好了,开始开会...");
       System.out.println("-------------------");
   });
   private static ExecutorService executorService = Executors.newFixedThreadPool(num);
   public static void main(String[] args) throws Exception{
       executorService.submit(() -> {
           System.out.println("A在上厕所");
           try {
               Thread.sleep(4000);
               System.out.println("A上完了");
               cyclicBarrier.await();
               System.out.println("会议结束,A退出");
           } catch (Exception e) {
               e.printStackTrace();
           }finally {

           }
       });
       executorService.submit(()->{
           System.out.println("B在上厕所");
           try {
               Thread.sleep(2000);
               System.out.println("B上完了");
               cyclicBarrier.await();
               System.out.println("会议结束,B退出");
           } catch (Exception e) {
               e.printStackTrace();
           }finally {

           }
       });
       executorService.submit(()->{
           System.out.println("C在上厕所");
           try {
               Thread.sleep(3000);
               System.out.println("C上完了");
               cyclicBarrier.await();
               System.out.println("会议结束,C退出");
           } catch (Exception e) {
               e.printStackTrace();
           }finally {

           }
       });

       executorService.shutdown();

   }}

输出结果为:

A在上厕所
B在上厕所
C在上厕所
B上完了
C上完了
A上完了
所有人都好了,开始开会...-------------------会议结束,A退出
会议结束,B退出
会议结束,C退出

从结果来看和CountDownLatch非常相似,初始化传入3个线程和一个任务,线程调用await()之后进入阻塞,计数器-1,当计数器为0时,就去执行CyclicBarrier中构造函数的任务,当任务执行完毕后,唤醒所有阻塞中的线程。这验证了CyclicBarrier让一组线程全部达到一个状态之后再全部同时执行的效果。

再举个例子来验证CyclicBarrier可重用的效果。

public class CyclicBarrierTest2 {
   private static int num = 3;
   private static CyclicBarrier cyclicBarrier = new CyclicBarrier(num, () -> {
       System.out.println("-------------------");
   });
   private static ExecutorService executorService = Executors.newFixedThreadPool(num);

   public static void main(String[] args) throws Exception {
       executorService.submit(() -> {
           System.out.println("A在上厕所");
           try {
               Thread.sleep(4000);
               System.out.println("A上完了");
               cyclicBarrier.await();
               System.out.println("会议结束,A退出,开始撸代码");
               cyclicBarrier.await();
               System.out.println("C工作结束,下班回家");
               cyclicBarrier.await();
           } catch (Exception e) {
               e.printStackTrace();
           } finally {

           }
       });
       executorService.submit(() -> {
           System.out.println("B在上厕所");
           try {
               Thread.sleep(2000);
               System.out.println("B上完了");
               cyclicBarrier.await();
               System.out.println("会议结束,B退出,开始摸鱼");
               cyclicBarrier.await();
               System.out.println("B摸鱼结束,下班回家");
               cyclicBarrier.await();
           } catch (Exception e) {
               e.printStackTrace();
           } finally {

           }
       });
       executorService.submit(() -> {
           System.out.println("C在上厕所");
           try {
               Thread.sleep(3000);
               System.out.println("C上完了");
               cyclicBarrier.await();
               System.out.println("会议结束,C退出,开始摸鱼");
               cyclicBarrier.await();
               System.out.println("C摸鱼结束,下班回家");
               cyclicBarrier.await();
           } catch (Exception e) {
               e.printStackTrace();
           } finally {

           }
       });

       executorService.shutdown();

   }}

输出结果:

A在上厕所
B在上厕所
C在上厕所
B上完了
C上完了
A上完了-------------------会议结束,A退出,开始撸代码
会议结束,B退出,开始摸鱼
会议结束,C退出,开始摸鱼-------------------C摸鱼结束,下班回家
C工作结束,下班回家
B摸鱼结束,下班回家-------------------

从结果来看,每个子线程调用await()计数器减为0之后才开始继续一起往下执行,会议结束之后一起进入摸鱼状态,最后一天结束一起下班,这就是可重用。

CyclicBarrier还是基于AQS实现的,内部维护parties记录总线程数,count用于计数,最开始count=parties,调用await()之后count原子递减,当count为0之后,再次将parties赋值给count,这就是复用的原理。

  1. 当子线程调用await()方法时,获取独占锁,同时对count递减,进入阻塞队列,然后释放锁

  2. 当第一个线程被阻塞同时释放锁之后,其他子线程竞争获取锁,操作同1

  3. 直到最后count为0,执行CyclicBarrier构造函数中的任务,执行完毕之后子线程继续向下执行

Semaphore

Semaphore叫做信号量,和前面两个不同的是,他的计数器是递增的。

public class SemaphoreTest {
   private static int num = 3;
   private static int initNum = 0;
   private static Semaphore semaphore = new Semaphore(initNum);
   private static ExecutorService executorService = Executors.newFixedThreadPool(num);
   public static void main(String[] args) throws Exception{
       executorService.submit(() -> {
           System.out.println("A在上厕所");
           try {
               Thread.sleep(4000);
               semaphore.release();
               System.out.println("A上完了");
           } catch (Exception e) {
               e.printStackTrace();
           }finally {

           }
       });
       executorService.submit(()->{
           System.out.println("B在上厕所");
           try {
               Thread.sleep(2000);
               semaphore.release();
               System.out.println("B上完了");
           } catch (Exception e) {
               e.printStackTrace();
           }finally {

           }
       });
       executorService.submit(()->{
           System.out.println("C在上厕所");
           try {
               Thread.sleep(3000);
               semaphore.release();
               System.out.println("C上完了");
           } catch (Exception e) {
               e.printStackTrace();
           }finally {

           }
       });

       System.out.println("等待所有人从厕所回来开会...");
       semaphore.acquire(num);
       System.out.println("所有人都好了,开始开会...");

       executorService.shutdown();

   }}

输出结果为:

A在上厕所
B在上厕所
等待所有人从厕所回来开会...C在上厕所
B上完了
C上完了
A上完了
所有人都好了,开始开会...

稍微和前两个有点区别,构造函数传入的初始值为0,当子线程调用release()方法时,计数器递增,主线程acquire()传参为3则说明主线程一直阻塞,直到计数器为3才会返回。

Semaphore还还还是基于AQS实现的,同时获取信号量有公平和非公平两种策略

  1. 主线程调用acquire()方法时,用当前信号量值-需要获取的值,如果小于0,则进入同步阻塞队列,大于0则通过CAS设置当前信号量为剩余值,同时返回剩余值

  2. 子线程调用release()给当前信号量值计数器+1(增加的值数量由传参决定),同时不停的尝试因为调用acquire()进入阻塞的线程

总结

CountDownLatch通过计数器提供了比join更灵活的多线程控制方式,CyclicBarrier也可以达到CountDownLatch的效果,而且有可复用的特点,Semaphore则是采用信号量递增的方式,开始的时候并不需要关注需要同步的线程个数,并且提供获取信号的公平和非公平策略。

最后

感谢大家看到这里,文章有不足,欢迎大家指出;如果你觉得写得不错,那就给我一个赞吧。


Acho que você gosta

Origin blog.51cto.com/14849432/2542962
Recomendado
Clasificación