Java并发专题之八juc-locks之synchronizer框架
Posted cac2020
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java并发专题之八juc-locks之synchronizer框架相关的知识,希望对你有一定的参考价值。
环境
jdk version:jdk1.8.0_171
一、CountDownLatch
CountDownLatch是一个辅助同步器类,用来作计数使用,它的作用类似于生活中的倒数计数器,先设定一个计数初始值,当计数降到0时,将会触发一些事件,如火箭的倒数计时。
初始计数值在构造CountDownLatch对象时传入,每调用一次 countDown() 方法,计数值就会减1。线程可以调用CountDownLatch的await方法进入阻塞,当计数值降到0时,所有之前调用await阻塞的线程都会释放。
注意:CountDownLatch的初始计数值一旦降到0,无法重置。如果需要重置,可以考虑使用CyclicBarrier。
1、应用场景
(1)典型用法1:
某一线程在开始运行前等待n个线程执行完毕。
将CountDownLatch的计数器初始化为n:new CountDownLatch(n) ,每当一个任务线程执行完毕,就将计数器减1, countdownlatch.countDown(),当计数器的值变为0时,在CountDownLatch上 await() 的线程就会被唤醒。
一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。
示例:
package test; import java.util.Vector; import java.util.concurrent.CountDownLatch; public class CountDownLatchDemo { private final static CountDownLatch cdl = new CountDownLatch(3); private final static Vector v = new Vector(); private static class WriteThread extends Thread { private final String writeThreadName; private final int stopTime; private final String str; public WriteThread(String name, int time, String str) { this.writeThreadName = name; this.stopTime = time; this.str = str; } public void run() { System.out.println(writeThreadName + "开始写入工作"); try { Thread.sleep(stopTime); } catch (InterruptedException e) { e.printStackTrace(); } cdl.countDown(); v.add(str); System.out.println(writeThreadName + "写入内容为:" + str + "。写入工作结束!"); } } private static class ReadThread extends Thread { public void run() { System.out.println("读操作之前必须先进行写操作"); try { cdl.await();//该线程进行等待,直到countDown减到0,然后逐个苏醒过来。 //Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 0; i < v.size(); i++) { System.out.println("读取第" + (i + 1) + "条记录内容为:" + v.get(i)); } System.out.println("读操作结束!"); } } public static void main(String[] args) { new ReadThread().start(); new WriteThread("writeThread1", 1000, "多线程知识点").start(); new WriteThread("writeThread2", 2000, "多线程CountDownLatch的知识点").start(); new WriteThread("writeThread3", 3000, "多线程中控制顺序可以使用CountDownLatch").start(); } }
(2)典型用法2:
实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。做法是初始化一个共享的CountDownLatch(1),将其计数器初始化为1,多个线程在开始执行任务前首先 coundownlatch.await(),当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。
示例:
package test; import java.util.concurrent.CountDownLatch; public class Driver { CountDownLatch switcher = new CountDownLatch(1); public void run() { try { switcher.await(); //所有执行线程在此处等待开关开启 System.out.println("执行任务:"+Thread.currentThread().getName());; } catch (InterruptedException ex) { } } public static void main(String[] args) { Driver driver = new Driver(); for (int i = 0; i < 10; ++i) { new Thread(driver::run).start(); } System.out.println("主线程 开始开启开关"); driver.switcher.countDown(); // 主线程开启开关 // 主线程 开始开启开关 // 执行任务:Thread-0 // 执行任务:Thread-5 // 执行任务:Thread-1 // 执行任务:Thread-9 // 执行任务:Thread-3 // 执行任务:Thread-7 // 执行任务:Thread-2 // 执行任务:Thread-4 // 执行任务:Thread-6 // 执行任务:Thread-8 } }
3、原理
CountDownLatch通过AQS的共享锁来实现:
CountDownLatch创建时根据传入的倒计时数作为AQS的state值,state != 0; 所有调用await的线程由于(getState() == 0) ? 1 : -1 全部返回-1,获取锁失败,线程被包装成状态为SINGNAL node节点进入CLH队列阻塞自旋; 当调用countDown将state减小为0时,从头节点(Dummy节点状态也为-1)开始唤醒CLH队列首节点,然后依次唤醒后续SINGNAL节点; 唤醒后从阻塞处(await)继续执行;
参考:
二、CyclicBarrier【人满发车】
CyclicBarrier可认为是一个栅栏,栅栏的作用是什么?就是阻挡前行。顾名思义,CyclicBarrier是一个可以循环使用的栅栏,它做的事情就是:
让线程到达栅栏时被阻塞(调用await方法),直到到达栅栏的线程数满足指定数量要求时,栅栏才会打开放行。
可以看下面这个图来理解下:
一共4个线程A、B、C、D,它们到达栅栏的顺序可能各不相同。当A、B、C到达栅栏后,由于没有满足总数【4】的要求,所以会一直等待,当线程D到达后,栅栏才会放行。
1、应用场景
多线程分组计算:多线程计算数据,最后合并计算结果的应用场景。
2、示例
比如现在需要计算2个人12个月内的工资详细,可以将线程分为2个,分别计算每个人的工资,最后,再用barrierAction将这些线程的计算结果进行整合,得出最后结果。
package test; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class CyclicBarrierDemo { /** * @Desc: * 计算出员工1的工资 * 计算出员工2的工资 * 汇总已分别计算出的两个员工的工资 */ public static void main(String[] args) throws InterruptedException{ CyclicBarrier cyclicBarrier = new CyclicBarrier(2, new Runnable() { @Override public void run() { System.out.println("汇总已分别计算出的两个员工的工资"); } }); Thread thread1 = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("计算出员工1的工资"); try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } }, "thread1"); Thread thread2 = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("计算出员工2的工资"); try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } }, "thread2"); thread1.start(); thread2.start(); } }
3、原理
主要是用ReentrantLock和ReentrantLock创建的Condition来实现栅栏的:
之前到达的子线程调用一次await,会被封装成node进入Condition队列;
最后一个线程调用await后,执行指定的Runnable任务;
然后将Condition队列中所有节点唤醒进入CLH队列,依次执行各自线程await()之后的代码;
4、BrokenBarrierException异常
线程调用await进入Condition队列处于阻塞状态,如果线程出现中断、超时、reset时,那么达不到CyclicBarrier放行的条件,其他线程会一直处于阻塞而无法执行。
CyclicBarrier引入BrokenBarrierException异常,表示当前的CyclicBarrier已经损坏了,可能等不到所有线程都到达栅栏了,所以已经在等待的线程也没必要再等了,可以散伙了。
就会将栅栏置为损坏(Broken)状态,并传播BrokenBarrierException给其它所有正在等待(await)的线程。
示例:
package test; import java.util.ArrayList; import java.util.List; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class CyclicBarrierBrokenTest { CyclicBarrier cb = new CyclicBarrier(5, new Runnable() { @Override public void run() { System.out.println("****** 所有运动员已准备完毕,发令枪:跑!******"); } }); public void run (){ try { System.out.println(Thread.currentThread().getName() + ": 准备完成"); cb.await(); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + ": 被中断"); } catch (BrokenBarrierException e) { System.out.println(Thread.currentThread().getName() + ": 抛出BrokenBarrierException"); } } public static void main(String[] args) throws InterruptedException { CyclicBarrierBrokenTest test = new CyclicBarrierBrokenTest(); List<Thread> list = new ArrayList<>(); for (int i = 0; i < 5; i++) { Thread t = new Thread(test::run, "运动员[" + i + "]"); list.add(t); t.start(); if (i == 3) { t.interrupt(); // 运动员[3]置中断标志位 } } Thread.sleep(2000); System.out.println("Barrier是否损坏:" + test.cb.isBroken()); // 运动员[0]: 准备完成 // 运动员[2]: 准备完成 // 运动员[4]: 准备完成 // 运动员[1]: 准备完成 // 运动员[3]: 准备完成 // 运动员[3]: 被中断 // 运动员[0]: 抛出BrokenBarrierException // 运动员[2]: 抛出BrokenBarrierException // 运动员[4]: 抛出BrokenBarrierException // 运动员[1]: 抛出BrokenBarrierException // Barrier是否损坏:true } }
5、CountDownLatch与CyclicBarrier区别:
CountDownLatch是一个同步的辅助类,允许一个或多个线程,等待其他一组线程完成操作,再继续执行。
CyclicBarrier是一个同步的辅助类,允许一组线程相互之间等待,达到一个共同点,再继续执行。
区别:
CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset() 方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次
CyclicBarrier还提供getNumberWaiting(可以获得CyclicBarrier阻塞的线程数量)、isBroken(用来知道阻塞的线程是否被中断)等方法。
CountDownLatch会阻塞主线程,CyclicBarrier不会阻塞主线程,只会阻塞子线程。
三、Semaphore
Semaphore,信号量或许可证。
当出于系统性能的考虑需要限流,或者合理使用是稀缺的共享资源时,需要一种机制控制同时访问共享资源的最大线程数量。Semaphore维护了一个许可集,其实就是一定数量的“许可证”。
当有线程想要访问共享资源时,需要先获取许可(取得授权);如果许可不够了,线程需要一直等待,直到许可可用。当线程使用完共享资源后,可以归还许可(去掉权限),以供其它需要的线程使用。
1、应用场景
用于限制获取某种资源的线程数量。
示例
package test; import java.util.concurrent.Semaphore; public class SemaphoreDemo { Semaphore semaphore = new Semaphore(2,true); private void runa(){ try { System.out.println("A开始...."); semaphore.acquire(); Thread.sleep(60*60*1000); semaphore.release(); System.out.println("A结束...."); } catch (Exception e){ } } private void runb(){ try { System.out.println("B开始...."); semaphore.acquire(2); Thread.sleep(60*60*1000); semaphore.release(2); System.out.println("B结束...."); } catch (Exception e){ } } private void runc(){ try { System.out.println("C开始...."); semaphore.acquire(); Thread.sleep(60*60*1000); semaphore.release(); System.out.println("C结束...."); } catch (Exception e){ } } public static void main(String[] args) throws Exception { SemaphoreDemo demo = new SemaphoreDemo(); new Thread(demo::runa).start(); Thread.sleep(2000); new Thread(demo::runb).start(); new Thread(demo::runc).start(); } }
2、原理
Semaphore是通过内部类实现了AQS框架,而且基本结构几乎和ReentrantLock完全一样,通过内部类分别实现了公平/非公平策略。
acquire用于获取许可,release用于释放许可;
acquire,当许可数够用就直接运行,许可数相应减少;
acquire,当许可数不够用就封装成shared共享节点计入到CLH队列阻塞,当使用公平策略,后续线程检测到CLH队列前面有等待节点,也会加入到CLH队列,不管许可数是否够用。
release,线程会从原先阻塞处继续执行。
参考:
Semaphore
四、Exchanger
Exchanger:交换器,主要作用是交换数据。
Exchanger类似CyclicBarrier,可以看成是一个双向栅栏:
Thread1线程到达栅栏后,会首先观察有没其它线程已经到达栅栏,如果没有就会等待,如果已经有其它线程(Thread2)已经到达了,就会以成对的方式交换各自携带的信息,因此Exchanger非常适合用于两个线程之间的数据交换。
1、应用场景
示例
毒贩和贩毒的人,会先约定某个地点,然后进行一手交钱一手交白粉的勾当:
package test; import java.util.concurrent.Exchanger; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ExchangerDemo { public static void main(String[] args) { // 新建一个Exchanger final Exchanger<String> exchanger = new Exchanger<String>(); // 新建一个线程,该线程持有资源为白粉 ExecutorService service = Executors.newFixedThreadPool(1); service.execute(new Runnable() { @Override public void run() { try { System.out.println(Thread.currentThread().getName()+"我有白粉,准备交换钱……"); Thread.sleep(1); /* * 在此处等待另外一个线程到来,并进行数据交换,如果没有另一个线程到来,那么当前这个线程会处于休眠状态,直到3件事情发生: * 1、等待另一个线程到达交换点 * 2、被另一个线程中断(警察赶来了,打断了交易) * 3、等待超时,当调用exchanger.exchange(x, timeout, unit)方法时有效(毒贩查觉到危险,没有来交易) */ String result = exchanger.exchange("白粉"); System.out.println(Thread.currentThread().getName()+"换回来的为:"+" "+result+" 原来为白粉!"); } catch (InterruptedException e) { e.printStackTrace(); } } }); // 新建一个线程,该线程持有资源为钱 ExecutorService service1 = Executors.newFixedThreadPool(1); service1.execute(new Runnable() { @Override public void run() { try { System.out.println(Thread.currentThread().getName()+"我有钱,准备交换白粉"); Thread.sleep(10000); String result = exchanger.exchange("钱"); System.out.println(Thread.currentThread().getName()+"换回来的为:"+" "+result+" 原来为钱!"); } catch (InterruptedException e) { e.printStackTrace(); } } }); // 释放线程资源 service.shutdown(); service1.shutdown(); } }
2、原理
2.1 对外接口
Exchanger<V> 泛型类型,其中 V 表示可交换的数据类型;
exchange(V v):等待另一个线程到达此交换点(除非当前线程被中断),然后将给定的对象传送给该线程,并接收该线程的对象。
exchange(V v, long timeout, TimeUnit unit):等待另一个线程到达此交换点(除非当前线程被中断或超出了指定的等待时间),然后将给定的对象传送给该线程,并接收该线程的对象。
2.2 无锁+分散热点
第一个线程到来:Exchanger创建一个空Node节点,item存放当前先到达的线程数据,将Node设置到slot;
第一个线程等待过程:先自旋,然后Thread.yeald(),然后park;
第二个线程到来:发现slot已被设置,将slot置空;
将slot的item返回,作为后来线程交换成功后的数据;
将后来线程的数据设置到match;
将parked对应先来线程唤醒;
参考:
Exchanger
五、Phaser
Phaser(JDK1.7引入)适用于一些需要分阶段的任务的处理。功能与CyclicBarrier和CountDownLatch有些类似,类似于一个多阶段的栅栏,并且功能更强大。
1、应用场景
Phaser与阶段相关,比较适合这样一种场景:一批任务可以分为多个阶段,现希望多个线程去处理该批任务,对于每个阶段,多个线程可以并发进行,但是希望保证只有前面一个阶段的任务完成之后才能开始后面的任务。
示例一:通过Phaser控制多个线程的执行时机:所有线程到达指定点后再同时开始执行,也可以利用CyclicBarrier或CountDownLatch来实现
package test; import java.util.concurrent.Phaser; public class Task implements Runnable{ private final Phaser phaser; Task(Phaser phaser) { this.phaser = phaser; } @Override public void run() { // if ("Thread-0".equals(Thread.currentThread().getName())){ // System.out.println("0"); // } // else { // try { // Thread.sleep(60*60*1000); // } catch (InterruptedException e) { // e.printStackTrace(); // } // } /** * arriveAndAwaitAdvance:调用一次arriveAndAwaitAdvance就是一次阶段 * 非最后到达线程调用internalAwaitAdvance,先进行自旋,然后在封装成QNode加入无所栈(一个阶段的节点只放在一个栈),调用block阻塞; * 最后到达的线程执行onAdvance(不会加入无锁栈),然后调用releaseWaiters,循环释放无所栈里的所有QNode里的线程Thread,从阻塞出唤醒执行; * */ int i = phaser.arriveAndAwaitAdvance(); // 等待其它参与者线程到达 // do something System.out.println(Thread.currentThread().getName() + ": 执行完任务,当前phase =" + i + ""); } }
package test; import java.util.concurrent.Phaser; public class PhaserTest1 { public static void main(String[] args) { /* Phaser构造函数工作: 初始化参与者数目:parties 初始化父Phaser:parent 初始化根Phaser:root 初始化奇偶栈:evenQ、oddQ 初始化同步状态:state */ Phaser phaser = new Phaser();//可以传入参与者数目 for (int i = 0; i < 10; i++) { // 注册各个参与者线程 phaser.register();//register() 注册1个参与者 主要工作:修改state值 new Thread(new Task(phaser), "Thread-" + i).start(); } } }
结果:
Thread-9: 执行完任务,当前phase =1 Thread-3: 执行完任务,当前phase =1 Thread-7: 执行完任务,当前phase =1 Thread-2: 执行完任务,当前phase =1 Thread-6: 执行完任务,当前phase =1 Thread-0: 执行完任务,当前phase =1 Thread-1: 执行完任务,当前phase =1 Thread-8: 执行完任务,当前phase =1 Thread-4: 执行完任务,当前phase =1 Thread-5: 执行完任务,当前phase =1
以上示例中,创建了10个线程,并通过register
方法注册Phaser的参与者数量为10。当某个线程调用arriveAndAwaitAdvance
方法后,arrive数量会加1,如果数量没有满足总数(参与者数量10),当前线程就是一直等待,当最后一个线程到达后,所有线程都会继续往下执行。
示例二:通过Phaser实现开关,类似CountDownLatch.
package test; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.concurrent.Phaser; public class PhaserTest2 { public static void main(String[] args) throws IOException { Phaser phaser = new Phaser(1);//构造数1 相当于初始化参与数1 for (int i = 0; i < 10; i++) { // 注册10个参与者线程 这样总数就是11个参与者 phaser.register(); //注册1个参与者 修改state值 new Thread(new Task(phaser), "Thread-" + i).start(); } // 外部条件:等待用户输入命令 System.out.println("Press ENTER to continue"); BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); reader.readLine(); // 打开开关(相当于总参与者减1,由11个减少1个位10个 那么满足跃迁条件 相当于打开开关) //arriveAndDeregister:注销1个参与者 //当未到达参与数等于1时,执行onAdvance(不会加入无锁栈),然后调用releaseWaiters,循环释放无所栈里的所有QNode里的线程Thread,从阻塞出唤醒执行 phaser.arriveAndDeregister(); System.out.println("主线程打开了开关"); } }
运行结果:
Press ENTER to continue 主线程打开了开关 Thread-7: 执行完任务,当前phase =1 Thread-3: 执行完任务,当前phase =1 Thread-8: 执行完任务,当前phase =1 Thread-4: 执行完任务,当前phase =1 Thread-0: 执行完任务,当前phase =1 Thread-6: 执行完任务,当前phase =1 Thread-2: 执行完任务,当前phase =1 Thread-9: 执行完任务,当前phase =1 Thread-5: 执行完任务,当前phase =1 Thread-1: 执行完任务,当前phase =1
示例三:通过Phaser控制任务的执行轮数.
package test; import java.util.concurrent.Phaser; public class Task3 implements Runnable{ private final Phaser phaser; Task3(Phaser phaser) { this.phaser = phaser; } @Override public void run() { ////只要Phaser没有终止, 各个线程的任务就会一直执行 while (!phaser.isTerminated()) { // 等待其它参与者线程到达 // 最后到达的线程会更新nextPhase、nextUnarrived,最终更新state 然后下一轮才会继续进行 int i = phaser.arriveAndAwaitAdvance(); // do something System.out.println(Thread.currentThread().getName() + ": 执行完任务"); } } }
package test; import java.io.IOException; import java.util.concurrent.Phaser; public class PhaserTest3 { public static void main(String[] args) throws IOException { int repeats = 3; // 指定任务最多执行的次数 Phaser phaser = new Phaser() { //创建Phaser对象时,覆写了onAdvance方法,这个方法类似于CyclicBarrier中的barrierAction任务。 @Override protected boolean onAdvance(int phase, int registeredParties) { System.out.println("---------------PHASE[" + phase + "],Parties[" + registeredParties + "] ---------------"); //registeredParties表示到达时的参与者数量,返回true表示需要终止Phaser //通过phase + 1 >= repeats ,来控制阶段(phase)数的上限为2(从0开始计),最终控制了每个线程的执行任务次数为repeats次。 return phase + 1 >= repeats || registeredParties == 0; } }; for (int i = 0; i < 10; i++) { phaser.register(); // 注册各个参与者线程 new Thread(new Task3(phaser), "Thread-" + i).start(); } } }
---------------PHASE[0],Parties[10] --------------- Thread-7: 执行完任务 Thread-8: 执行完任务 Thread-2: 执行完任务 Thread-1: 执行完任务 Thread-0: 执行完任务 Thread-6: 执行完任务 Thread-4: 执行完任务 Thread-3: 执行完任务 Thread-5: 执行完任务 Thread-9: 执行完任务 ---------------PHASE[1],Parties[10] --------------- Thread-6: 执行完任务 Thread-0: 执行完任务 Thread-2: 执行完任务 Thread-4: 执行完任务 Thread-5: 执行完任务 Thread-1: 执行完任务 Thread-9: 执行完任务 Thread-7: 执行完任务 Thread-3: 执行完任务 Thread-8: 执行完任务 ---------------PHASE[2],Parties[10] --------------- Thread-8: 执行完任务 Thread-5: 执行完任务 Thread-4: 执行完任务 Thread-0: 执行完任务 Thread-7: 执行完任务 Thread-1: 执行完任务 Thread-3: 执行完任务 Thread-9: 执行完任务 Thread-6: 执行完任务 Thread-2: 执行完任务
示例四:Phaser支持分层功能.
树:所有phaser共用root的无锁栈evenQ、oddQ
分层:Phaser1对应4个参与者,Phaser2对应4个参与者,Phaser3对应2个参与者,而Phaser对应3个参与者 就是3个二级Phaser
当二级Phaser执行arriveAndAwaitAdvance时,会调用parent.arriveAndAwaitAdvance() 进行到达参与者数目的调整
package test; import java.util.concurrent.Phaser; public class Task4 implements Runnable{ private final int id; private final Phaser phaser; public Task4(int id, Phaser phaser) { this.id = id; this.phaser = phaser; this.phaser.register(); } @Override public void run() { while (!phaser.isTerminated()) { try { Thread.sleep(200); } catch (InterruptedException e) { } System.out.println("in Task.run(), phase: " + phaser.getPhase() + ", id: " + this.id); //子节点会调用父节点parent.arriveAndAwaitAdvance() 进行到达参与者的同步 phaser.arriveAndAwaitAdvance(); } } }
package test; import java.io.IOException; import java.util.concurrent.Phaser; public class PhaserTest4 { private static final int TASKS_PER_PHASER = 4; public static void main(String args[]) throws Exception { final int phaseToTerminate = 3; //创建一个Phaser父类对象 final Phaser phaser = new Phaser() { @Override protected boolean onAdvance(int phase, int registeredParties) { //屏障方法 System.out.println("====== " + phase + " ======"); return phase == phaseToTerminate || registeredParties == 0; } }; //创建10个任务数组 final Task4 tasks[] = new Task4[10]; //10个任务分层 build(tasks, 0, tasks.length, phaser); //启动线程 for (int i = 0; i < tasks.length; i++) { System.out.println("starting thread, id: " + i); final Thread thread = new Thread(tasks[i]); thread.start(); } } //递归分层, public static void build(Task4[] tasks, int lo, int hi, Phaser ph) { //如果任务的数量超过每一层的phaser的阈值TASKS_PER_PHASER,则要继续分层 if (hi - lo > TASKS_PER_PHASER) { for (int i = lo; i < hi; i += TASKS_PER_PHASER) { int j = Math.min(i + TASKS_PER_PHASER, hi); //当前的phaser(ph)作为父,来创建一个子phaser build(tasks, i, j, new Phaser(ph)); } }以上是关于Java并发专题之八juc-locks之synchronizer框架的主要内容,如果未能解决你的问题,请参考以下文章