【并发编程】JUC并发编程(彻底搞懂JUC)
文章目录
- 一、背景
- 二、什么是JUC?
- 三、JUC框架结构
- 四、JUC框架概述
- 五、JUC中常用类汇总
- 六、相关名词
- 进程和线程
- 进程
- 线程
- 创建线程的几种常见的方式
- 并发和并行
- 用户线程和守护线程
- 七、synchronized 作用范围:
- 八、Lock锁(重点)
- 什么是 Lock
- 锁类型
- Lock接口
- lock()、unlock()
- newCondition
- ReentrantLock (可重入锁)
- ReadWriteLock (读写锁)
- 案例
- Lock 与的 Synchronized 区别
- 九、Callable 接口
- 前言:
- 概述:
- 实现:
- 十、Future 接口
- 概述:
- 实现:
- FutureTask
- FutureTask介绍
- FutureTask应用场景及注意事项
- 使用 Callable 和 Future🚩
- 十一、JUC三大常用工具类
- CountDownLatch(减计数器)
- 概述:
- 案例:
- 小结:
- CyclicBarrier(加法计数器)
- 概述:
- 案例:
- 小结:
- Semaphore( 信号灯)
- 概述:
- 案例:
- 小结:
- 简单讲述 | Phaser & Exchanger
- Phaser
- Exchanger
- 十二、总结
一、背景
如果你对多线程没什么了解,那么从入门模块开始。 如果你已经入门了多线程(知道基础的线程创建、死锁、synchronized、lock等),那么从juc模块开始。
新手学技术、老手学体系,高手学格局。
二、什么是JUC?
JUC实际上就是我们对于jdk中java.util .concurrent 工具包的简称,其结构如下:
这个包下都是Java处理线程相关的类,自jdk1.5后出现。目的就是为了更好的支持高并发任务。让开发者进行多线程编程时减少竞争条件和死锁的问题!
JUC主要是指JDK8中java.util.concurrent里提供的一系列线程并发工具,但是线程并发的问题远不止几个工具这么简单。要学习工具使用,更要能深入理解工具的原理以及处理线程并发问题的思路。
三、JUC框架结构
UC是包的简称,JUC可能也是Java核心里最难的一块儿,JUC指的是Java的并发工具包,里边提供了各种各样的控制同步和线程通信的工具类。学习JUC之前,最重要的是了解JUC的结构是什么样的。就如同Java的集合框架的结构一样,JUC也有自己框架结构,只是往往被大家忽略,笔者就简单的梳理了下JUC的框架结构,JUC的框架结构不同于集合,它并非是实现继承框架结构。
四、JUC框架概述
JUC框架的底层在Java代码里是Unsafe,而Unsafe是底层Jvm的实现。有了Unsafe的支持实现了一些了支持原子型操作的Atomic类,然后上层才有了我们熟知的AQS,和LockSupport等类。有了这些之后才是各种读写锁各种线程通信以及同步工具的实现类。
五、JUC中常用类汇总
-
JUC的atomic包下运用了CAS的AtomicBoolean、AtomicInteger、AtomicReference等原子变量类
-
JUC的locks包下的AbstractQueuedSynchronizer(AQS)以及使用AQS的ReentantLock(显式锁)、ReentrantReadWriteLock
-
附:运用了AQS的类还有:
Semaphore、CountDownLatch、ReentantLock(显式锁)、ReentrantReadWriteLock
-
JUC下的一些同步工具类:
CountDownLatch(闭锁)、Semaphore(信号量)、CyclicBarrier(栅栏)、FutureTask
-
JUC下的一些并发容器类:
ConcurrentHashMap、CopyOnWriteArrayList
-
读写分离:
CopyOnWriteArrayList
写操作在一个复制的数组上进行,读操作还是在原始数组中进行,读写分离,互不影响。
写操作需要加锁,防止并发写入时导致写入数据丢失。
写操作结束之后需要把原始数组指向新的复制数组。
-
JUC下的一些Executor框架的相关类:
线程池的工厂类->Executors 线程池的实现类->ThreadPoolExecutor/ForkJoinPool
-
JUC下的一些阻塞队列实现类:
ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue
- tools(工具类):又叫信号量三组工具类,包含有
-
CountDownLatch(闭锁) 是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待
-
CyclicBarrier(栅栏) 之所以叫barrier,是因为是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点 ,并且在释放等待线程后可以重用。
-
Semaphore(信号量) 是一个计数信号量,它的本质是一个“共享锁“。信号量维护了一个信号量许可集。线程可以通过调用 acquire()来获取信号量的许可;当信号量中有可用的许可时,线程能获取该许可;否则线程必须等待,直到有可用的许可为止。 线程可以通过release()来释放它所持有的信号量许可。
- executor(执行者):是Java里面线程池的顶级接口,但它只是一个执行线程的工具,真正的线程池接口是ExecutorService,里面包含的类有:
- ScheduledExecutorService 解决那些需要任务重复执行的问题
- ScheduledThreadPoolExecutor 周期性任务调度的类实现
-
atomic(原子性包):是JDK提供的一组原子操作类,包含有AtomicBoolean、AtomicInteger、AtomicIntegerArray等原子变量类,他们的实现原理大多是持有它们各自的对应的类型变量value,而且被volatile关键字修饰了。这样来保证每次一个线程要使用它都会拿到最新的值。
-
locks(锁包):是JDK提供的锁机制,相比synchronized关键字来进行同步锁,功能更加强大,它为锁提供了一个框架,该框架允许更灵活地使用锁包含的实现类有:
-
ReentrantLock 它是独占锁,是指只能被独自占领,即同一个时间点只能被一个线程锁获取到的锁。
-
ReentrantReadWriteLock 它包括子类ReadLock和WriteLock。ReadLock是共享锁,而WriteLock是独占锁。
-
LockSupport 它具备阻塞线程和解除阻塞线程的功能,并且不会引发死锁。
- collections(集合类):主要是提供线程安全的集合, 比如:
-
ArrayList对应的高并发类是CopyOnWriteArrayList,
-
HashSet对应的高并发类是 CopyOnWriteArraySet,
-
HashMap对应的高并发类是ConcurrentHashMap等等
六、相关名词
进程和线程
进程
概述:
进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。 在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
定义:
狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
线程
线程(thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之 中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流, 一个进程中可以并发多个线程,每条线程并行执行不同的任务。
- 线程是独立调度和分派的基本单位。
- 同一进程中的多条线程将共享该进程中的全部系统资源。
- 一个进程可以有很多线程,每条线程并行执行不同的任务。可并发执行。
当然,我们Java都知道的线程的同步是Java多线程编程的难点,因为要给哪些地方是共享资源(竞争资源),什么时候要考虑同步等,都是编程中的难点。
创建线程的几种常见的方式
- 通过实现Runnable接口来创建Thread线程
- 通过继承Thread类来创建一个线程
- 通过实现Callable接口来创建Thread线程
备注:详细看我写的上一篇文章:Java实现多线程的4种方式
并发和并行
在了解并发和并行之前,让我们先来看一看串行是什么样的吧。
- 串行模式:
- 串行模式:即表示所有任务都是按先后顺序进行。串行是一次只能取的一个任务,并执行这个任务。
- 举个生活中的小例子:就是在火车站买票,今天只开放这一个窗口卖票,那么我们只有等到前面的人都买了,才能轮到我们去买。即按先后顺序买到票。
- 并行模式:
- 概述:一组程序按独立异步的速度执行,无论从微观还是宏观,程序都是一起执行的。对比地,并发是指:在同一个时间段内,两个或多个程序执行,有时间上的重叠(宏观上是同时,微观上仍是顺序执行)。
- 并行模式:并行意味着可以同时取得多个任务,并同时去执行所取得的这些任务。
我们还是用上面那个例子:还是在买票,以前是只有一个窗口卖票,但是近几年发展起来了,现在有五个窗口卖票啦,大大缩短了人们买票的时间。
- 并行模式相当于将长长的一条队列,划分成了多条短队列,所以并行缩短了任务队列的长度。不过并行的效率,一方面受多进程/线程编码的好坏的影响,另一方面也受硬件角度上的CPU的影响。
- 并发:
- 并发:并发指的是多个程序可以同时运行的一种现象,并发的重点在于它是一种现象,并发描述的是多进程同时运行的现象。但真正意义上,一个单核心CPU任一时刻都只能运行一个线程。所以此处的"同时运行"表示的不是真的同一时刻有多个线程运行的现象(这是并行的概念),而是提供了一种功能让用户看来多个程序同时运行起来了,但实际上这些程序中的进程不是一直霸占 CPU 的,而是根据CPU的调度,执行一会儿停一会儿。
- 小小的总结一下:
-
并发:即同一时刻多个线程在访问同一个资源,多个线程对一个点
例子:秒杀活动、12306抢回家的票啦、抢演唱会的票…
-
并行:多个任务一起执行,之后再汇总
例子:电饭煲煮饭、用锅炒菜,两个事情一起进行,(最后我们一起干饭啦干饭啦😁)
用户线程和守护线程
-
用户线程:指不需要内核支持而在用户程序中实现的线程,其不依赖于操作系统核心,应用进程利用线程库提供创建、同步、调度和管理线程的函数来控制用户线程。
-
守护线程:是指在程序运行的时候在后台提供一种通用服务的线程,用来服务于用户线程;不需要上层逻辑介入,当然我们也可以手动创建一个守护线程。(用白话来说:就是守护着用户线程,当用户线程死亡,守护线程也会随之死亡)
比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
用一个简单代码来模拟一下:
未设置为守护线程时:主线程执行完成了,但是我们自己创建的线程仍然未结束。
设置为守护线程后:明显可以看到,当主线程执行完成后,我们设置为守护线程的那个线程也被强制结束了。
setDaemon就是设置为是否为守护线程。
七、synchronized 作用范围:
synchronized 是 Java 中的关键字,是一种同步锁。它修饰的对象有以下几种:
- 修饰某一处代码块,被修饰的代码块称为同步语句块。作用范围就是{}之间。作用的对象是调用这个代码块的对象。
synchronized (this){ System.out.println("同步代码块 "); }
- 修饰在方法上,被修饰的方法就称为同步方法。作用范围则是整个方法。作用的对象则是调用这个方法的对象。
public synchronized void sale() { }
注:synchronized 关键字不能被继承,如果父类中某方法使用了synchronized 关键字,字类又正巧覆盖了,此时,字类默认情况下是不同步的,必须显示的在子类的方法上加上才可。当然,如果在字类中调用父类中的同步方法,这样虽然字类并没有同步方法,但子类调用父类的同步方法,子类方法也相当同步了。
- 修饰某个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象。
public static synchronized void test(){ }
- 修饰某个类,其作用的范围是 synchronized 后面括号括起来的部分,作用的对象是这个类的所有对象。
class Ticket { public void sale() { synchronized (Ticket.class) { } } }
八、Lock锁(重点)
什么是 Lock
Lock 锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。
锁类型
可重入锁:在执行对象中所有同步方法不用再次获得锁
可中断锁:在等待获取锁过程中可中断
公平锁: 按等待获取锁的线程的等待时间进行获取,等待时间长的具有优先获取锁权利
读写锁:对资源读取和写入的时候拆分为2部分处理,读的时候可以多线程一起读,写的时候必须同步地写
Lock接口
public interface Lock { void lock(); //获得锁。 /** 除非当前线程被中断,否则获取锁。 如果可用,则获取锁并立即返回。 如果锁不可用,则当前线程将出于线程调度目的而被禁用并处于休眠状态,直到发生以下两种情况之一: 锁被当前线程获取; 要么其他一些线程中断当前线程,支持中断获取锁。 如果当前线程: 在进入此方法时设置其中断状态; 要么获取锁时中断,支持中断获取锁, */ void lockInterruptibly() throws InterruptedException; /** 仅在调用时空闲时才获取锁。 如果可用,则获取锁并立即返回值为true 。 如果锁不可用,则此方法将立即返回false值。 */ boolean tryLock(); //比上面多一个等待时间 boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 解锁 void unlock(); //返回绑定到此Lock实例的新Condition实例。 Condition newCondition(); 。 }
下面讲几个常用方法的使用。
lock()、unlock()
lock()是最常用的方法之一,作用就是获取锁,如果锁已经被其他线程获得,则当前线程将被禁用以进行线程调度,并处于休眠状态,等待,直到获取锁。
如果使用到了lock的话,那么必须去主动释放锁,就算发生了异常,也需要我们主动释放锁,因为lock并不会像synchronized一样被自动释放。所以使用lock的话,必须是在try{}catch(){}中进行,并将释放锁的代码放在finally{}中,以确保锁一定会被释放,以防止死锁现象的发生。
unlock()的作用就是主动释放锁。
lock接口的类型有好几个实现类,这里是随便找了个。
Lock lock = new ReentrantLock(); try { lock.lock(); System.out.println("上锁了"); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); System.out.println("解锁了"); }
newCondition
关键字 synchronized 与 wait()/notify()这两个方法一起使用可以实现等待/通知模式, Lock 锁的 newContition()方法返回 Condition 对象,Condition 类 也可以实现等待/通知模式。 用 notify()通知时,JVM 会随机唤醒某个等待的线程, 使用 Condition 类可以 进行选择性通知, Condition 比较常用的两个方法:
- await():会使当前线程等待,同时会释放锁,当等到其他线程调用signal()方法时,此时这个沉睡线程会重新获得锁并继续执行代码(在哪里沉睡就在哪里唤醒)。
- signal():用于唤醒一个等待的线程。
注意:在调用 Condition 的 await()/signal()方法前,也需要线程持有相关 的 Lock 锁,调用 await()后线程会释放这个锁,在调用singal()方法后会从当前 Condition对象的等待队列中,唤醒一个线程,后被唤醒的线程开始尝试去获得锁, 一旦成功获得锁就继续往下执行。
在这个地方我们举个例子来用代码写一下:
这里就不举例synchronized 实现了,道理都差不多。
例子:我们有两个线程,实现对一个初始值是0的number变量,一个线程当number = =0时 对number值+1,另外一个线程当number = = 1时对number-1。
class Share { private Integer number = 0; private ReentrantLock lock = new ReentrantLock(); private Condition newCondition = lock.newCondition(); // +1 的方法 public void incr() { try { lock.lock(); // 加锁 while (number != 0) { newCondition.await();//沉睡 } number++; System.out.println(Thread.currentThread().getName() + "::" + number); newCondition.signal(); //唤醒另一个沉睡的线程 } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } // -1 的方法 public void decr() { try { lock.lock(); while (number != 1) { newCondition.await(); } number--; System.out.println(Thread.currentThread().getName() + "::" + number); newCondition.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } } public class LockDemo2 { public static void main(String[] args) { Share share = new Share(); new Thread(()->{ for (int i=0;i share.incr(); } },"AA").start(); new Thread(()-{ for (int i=0;i share.decr(); } },"BB").start(); /** * AA::1 * BB::0 * AA::1 * BB::0 * ..... */ } } public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); new Thread(new Runnable() { @Override public void run() { try { lock.lock(); System.out.println("第1次获取锁,这个锁是:" + lock); for (int i = 2;i try { lock.lock(); System.out.println("第" + i + "次获取锁,这个锁是:" + lock); try { Thread.sleep(new Random().nextInt(200)); } catch (InterruptedException e) { e.printStackTrace(); } } finally { lock.unlock();// 如果把这里注释掉的话,那么程序就会陷入死锁当中。 } } } finally { lock.unlock(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { lock.lock(); System.out.println("这里是为了测试死锁而多写一个的线程"); } finally { lock.unlock(); } } }).start(); } } /** * 第1次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@6b5fde1f[Locked by thread Thread-0] * 第2次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@6b5fde1f[Locked by thread Thread-0] * 第3次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@6b5fde1f[Locked by thread Thread-0] * ... */ // 获取读锁 Lock readLock(); // 获取写锁 Lock writeLock(); } public static void main(String[] args) { final SynchronizedDemo2 test = new SynchronizedDemo2(); new Thread(()-{ test.get(Thread.currentThread()); }).start(); new Thread(()-{ test.get(Thread.currentThread()); }).start(); } public synchronized void get(Thread thread) { long start = System.currentTimeMillis(); while(System.currentTimeMillis() - start System.out.println(thread.getName()+"正在进行读操作"); } System.out.println(thread.getName()+"读操作完毕"); } } /** * 输出 * Thread-0正在进行读操作 * Thread-0读操作完毕 * Thread-1正在进行读操作 * Thread-1正在进行读操作 * Thread-1正在进行读操作 * .... * Thread-1读操作完毕 */ private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); public static void main(String[] args) { final SynchronizedDemo2 test = new SynchronizedDemo2(); new Thread(()-{ test.get2(Thread.currentThread()); }).start(); new Thread(()->{ test.get2(Thread.currentThread()); }).start(); } public void get2(Thread thread) { rwl.readLock().lock(); try { long start = System.currentTimeMillis(); while(System.currentTimeMillis() - start System.out.println(thread.getName()+"正在进行读操作"); } System.out.println(thread.getName()+"读操作完毕"); } finally { rwl.readLock().unlock(); } } } /** * 输出 * Thread-0正在进行读操作 * Thread-0读操作完毕 * Thread-1正在进行读操作 * Thread-1读操作完毕 */ V call() throws Exception; //计算结果,如果无法计算则抛出异常。 } public static void main(String[] args) { new Thread(new RunnableDemo1(),"AA").start(); FutureTask @Override public void run() { System.out.println(Thread.currentThread().getName()+"::通过实现Runnable来执行任务"); } } class CallableDemo @Override public java.lang.Integer call() throws Exception { System.out.println(Thread.currentThread().getName()+"::通过实现Callable接口来执行任务,并返回结果!"); return 1024; } } /** * AA::通过实现Runnable来执行任务 * BB::通过实现Callable接口来执行任务,并返回结果! * 1024 */ boolean cancel(boolean mayInterruptIfRunning); //尝试取消此任务的执行。 boolean isCancelled();//如果此任务在正常完成之前被取消,则返回true boolean isDone(); //如果此任务完成,则返回true 。 完成可能是由于正常终止、异常或取消——在所有这些情况下,此方法将返回true V get() throws InterruptedException, ExecutionException; //获得任务计算结果 V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;//可等待多少时间去获得任务计算结果 } public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask @Override public java.lang.Integer call() throws Exception { System.out.println(Thread.currentThread().getName()+"::通过实现Callable接口来执行任务,并返回结果!"); return 1024; } } public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException { CallableAndFutureTest callableAndFutureTest = new CallableAndFutureTest(); FutureTask @Override public String call() throws Exception { String str=""; for (int i=0;i str+=String.valueOf(i); Thread.sleep(100); } return str; } } sync.releaseShared(1); } sync.acquireSharedInterruptibly(1); } public static void main(String[] args) { // 初始值8 有八个人需要出寝室门 CountDownLatch countDownLatch = new CountDownLatch(8); for (int i = 1; i new Thread(() - { System.out.println(Thread.currentThread().getName() + "出去啦"); // 出去一个人计数器就减1 countDownLatch.countDown(); }, String.valueOf(i)).start(); } try { countDownLatch.await(); // 阻塞等待计数器归零 } catch (InterruptedException e) { e.printStackTrace(); } // 阻塞的操作 : 计数器 num++ System.out.println(Thread.currentThread().getName() + "====寝室人都已经出来了,关门向教室冲!!!===="); } } private int dowait(boolean timed, long nanos); // 供await方法调用 判断是否达到条件 可以往下执行吗 //创建一个新的CyclicBarrier,它将在给定数量的参与方(线程)等待时触发,每执行一次CyclicBarrier就累加1,达到了parties,就会触发barrierAction的执行 public CyclicBarrier(int parties, Runnable barrierAction) ; //创建一个新的CyclicBarrier ,参数就是目标障碍数,它将在给定数量的参与方(线程)等待时触发,每次执行 CyclicBarrier 一次障碍数会加一,如果达到了目标障碍数,才会执行 cyclicBarrier.await()之后的语句 public CyclicBarrier(int parties) //返回触发此障碍所需的参与方数量。 public int getParties() //等待,直到所有各方都在此屏障上调用了await 。 // 如果当前线程不是最后一个到达的线程,那么它会出于线程调度目的而被禁用并处于休眠状态.直到所有线程都调用了或者被中断亦或者发生异常中断退出 public int await() // 基本同上 多了个等待时间 等待时间内所有线程没有完成,将会抛出一个超时异常 public int await(long timeout, TimeUnit unit) //将障碍重置为其初始状态。 public void reset() } public static void main(String[] args) { // 第一个参数:目标障碍数 第二个参数:一个Runnable任务,当达到目标障碍数时,就会执行我们传入的Runnable // 当我们抽了201次的时候,就会执行这个任务。 CyclicBarrier cyclicBarrier = new CyclicBarrier(201,()-{ System.out.println("恭喜你,已经抽奖201次,幸运值已满,下次抽奖必中荣耀水晶!!!"); }); for (int i=1;i final int count=i; new Thread(()-{ System.out.println(Thread.currentThread().getName()+"抽奖一次"); try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } },String.valueOf(i)).start(); } } // 这行代码是重置计数 cyclicBarrier.reset(); // 这里是我又加了 一次循环, 可以看到最后结果中输出了两次 "恭喜你" for (int i=1;i final int count=i; new Thread(()-{ System.out.println(Thread.currentThread().getName()+"抽奖一次"); try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } },String.valueOf(i)).start(); } } public static void main(String[] args) { // 10台电脑 Semaphore semaphore = new Semaphore(10); // 20 个小伙伴想要上网 for (int i = 1; i new Thread(() - { try { //等待获取许可证 semaphore.acquire(); System.out.println(Thread.currentThread().getName() + "抢到了电脑"); //抢到的小伙伴,迅速就开打啦 这里就模拟个时间哈, TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } finally { //打完几把游戏的小伙伴 女朋友来找了 溜啦溜啦 希望大家都有人陪伴 System.out.println("女朋友来找,"+Thread.currentThread().getName() + "离开了"); semaphore.release();//释放资源,离开了就要把电脑让给别人啦。 } }, String.valueOf(i)).start(); } } } private static Phaser phaser = new MyPhaser(); //自定义一个移相器来自定义输出 static class MyPhaser extends Phaser { /** * @deprecated 在即将到来的阶段提前时执行操作并控制终止的可覆盖方法。 此方法在推进此移相器的一方到达时调用(当所有其他等待方处于休眠状态时)。 * 如果此方法返回true ,则此移相器将在提前时设置为最终终止状态,并且对isTerminated后续调用将返回 true。 * @param phase 进入此方法的当前阶段号,在此移相器前进之前 * @param registeredParties 当前注册方的数量 * @return */ @Override protected boolean onAdvance(int phase, int registeredParties) { if (phase == 0) { System.out.println("所有人都到达了网吧,准备开始开黑!!!"); return false; } else if (phase == 1) { System.out.println("大家都同意,一起去次烧烤咯!!!"); return false; } else if (phase == 2) { System.out.println("大家一起回寝室!!!"); return true; } return true; } } //构建一个线程任务 static class DoSomeThing implements Runnable { @Override public void run() { /** * 向此移相器添加一个新的未到达方 */ phaser.register(); System.out.println(Thread.currentThread().getName() + "从家里出发,准备去学校后街上网开黑!!!"); phaser.arriveAndAwaitAdvance(); System.out.println(Thread.currentThread().getName() + "上着上着饿了,说去次烧烤吗?"); phaser.arriveAndAwaitAdvance(); System.out.println(Thread.currentThread().getName() + "烧烤次完了"); phaser.arriveAndAwaitAdvance(); } } public static void main(String[] args) throws Exception { DoSomeThing thing = new DoSomeThing(); new Thread(thing, "小明").start(); new Thread(thing, "小王").start(); new Thread(thing, "小李").start(); } } /** * 小李从家里出发,准备去学校后街上网开黑!!! * 小王从家里出发,准备去学校后街上网开黑!!! * 小明从家里出发,准备去学校后街上网开黑!!! * 所有人都到达了网吧,准备开始开黑!!! * 小李上着上着饿了,说去次烧烤吗? * 小明上着上着饿了,说去次烧烤吗? * 小王上着上着饿了,说去次烧烤吗? * 大家都同意,一起去次烧烤咯!!! * 小明烧烤次完了 * 小李烧烤次完了 * 小王烧烤次完了 * 大家一起回寝室!!! */
-
-
-