Java并发编程之-了解CyclicBarrier

Posted yuxiaoming

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java并发编程之-了解CyclicBarrier相关的知识,希望对你有一定的参考价值。

起因一道面试题,关于concurrent包下的CyclicBarrier并发工具类,提出的一个情景题,因为不了解,所以花了一天的时间去学习并以此记录而诞生的这篇博客。

 

题目:假设有5个运动员(线程),让他们就绪在同一位置开始比赛跑步,当裁判(主线程)发出枪响号令。5个运动员(5个线程执行)开始奔跑,当5个线程跑到终点后。裁判(主线程执行)宣布比赛结果。

 

知识点:CyclicBarrier,CountDownLatch这两个位于JDK concurren包下的两个类。今天只实现CyclicBarrier这个类

原理:        CyclicBarrier大致是可循环利用的屏障,顾名思义,这个名字也将这个类的特点给明确地表示出来了。首先,便是可重复利用,说明该类创建的对象可以复用;其次,屏障则体现了该类的原理:每个线程执行时,都会碰到一个屏障,直到所有线程执行结束,然后屏障便会打开,使所有线程继续往下执行。

        这里介绍CyclicBarrier的两个构造函数:CyclicBarrier(int parties)和CyclicBarrier(int parties, Runnable barrierAction) :前者只需要声明需要拦截的线程数即可,而后者还需要定义一个等待所有线程到达屏障优先执行的Runnable对象。

        实现原理:在CyclicBarrier的内部定义了一个Lock对象,每当一个线程调用await方法时,将拦截的线程数减1,然后判断剩余拦截数是否为初始值parties,如果不是,进入Lock对象的条件队列等待。如果是,执行barrierAction对象的Runnable方法,然后将锁的条件队列中的所有线程放入锁等待队列中,这些线程会依次的获取锁、释放锁。

难点:

  1 :5个子线程执行运行完毕后,主线程执行其他任务方法,期间不能死亡。

  2:个人尝试使用线程池,来管理线程的执行。发现使用线程池子线程执行任务时,主线程会随时挂掉,这里我使用的睡眠sleep,但是我的“小伙伴“说这样不太好,这个问题暂时遗留记录

 

线程池版实现:

 1 import java.util.concurrent.*;
 2 
 3 /**
 4 * Java并发编程之,了解CyclicBarrier
 5 *
 6 * @Author: Mr.Tk
 7 * @Date: 2019-03-10 15:39
 8 */
 9 public class CyclicBarrierDemo {
10 
11     //创建一个线程池
12     private static final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5,10,60, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());
13 
14     //当拦截线程数量到达5,优先执行barrierAction参数线程方法,其次执行被拦截线程
15     private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5,new Runnable(){
16         @Override
17         public void run() {
18             System.out.println("裁判:预备... 跑!啾啪...枪声响起");
19         }
20     });
21 
22     private static class TestThread extends Thread {
23 
24         private String  name ;
25 
26         public TestThread(String name){
27             this.name=name;
28         }
29 
30         @Override
31         public void run() {
32             System.out.println("各就各位:"+name);
33             try {
34                 Thread.sleep(1000);
35                 //拦截线程
36                 cyclicBarrier.await();
37                 Thread.sleep(1000);
38                 System.out.println("到达终点:"+name);
39             } catch (InterruptedException e) {
40                 e.printStackTrace();
41             } catch (BrokenBarrierException e) {
42                 e.printStackTrace();
43             }
44         }
45     }
46 
47     public static void main(String[] args) {
48         String[] str = {"运动员1","运动员2","运动员3","运动员4","运动员5"};
49         for (int i=0 ; i<5 ;i++){
50             //调用线程
51             threadPool.execute(new TestThread(str[i]));
52 
53         }
54         try {
55             Thread.sleep(3000);//str.length*1000
56             System.out.println("裁判宣布成绩...hello world............");
57         } catch (InterruptedException e) {
58             e.printStackTrace();
59         }
60 
61 
62     }
63 }

 

"小伙伴"code非线程池版

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
* 非线程池操作
*
* @Author: Mr.Tk
* @Date: 2019-03-10 17:19
*/
public class CyclicBarrierDemo2 implements Runnable{

    //封装私有并发工具
    private CyclicBarrier cyclicBarrier;

    public CyclicBarrierDemo2(CyclicBarrier cyclicBarrier) {
        this.cyclicBarrier = cyclicBarrier;
    }

    @Override
    public void run() {
        try {
            System.out.println("ready");
            Thread.sleep(1000);
            cyclicBarrier.await();
            System.out.println("结束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //线程就绪5个后执行,线程方法run
        CyclicBarrierDemo2 cyclicBarrier = new CyclicBarrierDemo2(new CyclicBarrier(5, new Runnable() {
            @Override
            public void run() {
                System.out.println("裁判:预备... 跑!啾啪...枪声响起");
            }
        }));
        Thread thread1 = new Thread(cyclicBarrier);
        Thread thread2 = new Thread(cyclicBarrier);
        Thread thread3 = new Thread(cyclicBarrier);
        Thread thread4 = new Thread(cyclicBarrier);
        Thread thread5 = new Thread(cyclicBarrier);
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        thread5.start();

        //子线程执行完毕让每个子线程加入到主线程中。最后主线继续运行下去
        thread1.join();
        thread5.join();
        thread2.join();
        thread3.join();
        thread4.join();


        System.out.println("主线程执行完毕死亡,hello world ...");

    }

}

 

总结:从上代码看出,线程池的sleep方法暂时解决的主线程在自线程执行结束后,可以继续运行。但是就数据量大的处理来看,我门无法管理睡眠时间的长短。不能确定准确的睡眠时间所以存在风险。

  其二,如果我门不使用线程池来管理线程,面临的是内存性能的消耗,对于系统来说也是一笔开销。Condition这个锁对象是“小伙伴”提供给我的思路,暂时没有深入问题暂时遗留,有了解的可以私信讨论。

  个人设想:使用线程池管理线程时,将主线程挂起,将每个执行完毕的子线程(getCurrentThread()当前线程)执行等待方法(wait()),之后使用notifyAll()方法再将线程唤醒到主线程继续执行,这里又涉及到主线程等待后,子线程也全班等待后,谁将唤醒主线程的问题?如果让最后一个挂起线程唤醒主线程,具体怎么确定哪一个线程是执行到最后的?问题遗留。。。ing

注:不过就目前来看,该面试题是解决了... 之后会继续深入了解

以上是关于Java并发编程之-了解CyclicBarrier的主要内容,如果未能解决你的问题,请参考以下文章

java并发编程之三--CyclicBarrier的使用

Day838.CountDownLatch&CyclicBarrier-Java 并发编程实战

Day838.CountDownLatch&CyclicBarrier-Java 并发编程实战

Java并发多线程编程——并发工具类CyclicBarrier(回环栅栏)

并发编程系列之CyclicBarrier用法简介

并发编程系列之CyclicBarrier用法简介