线程控制工具类:
Condition 的使用及原理
synchronized 与 wait / notify 进行通信等待唤醒
lock 与 Condition 与其有类似作用
使用场景:生产者-消费者模型;有更强的控制性和扩展性
CountDownLatch 的原理分析
使用场景:倒计时计数器、模拟并发场景
CyclicBarrier 原理分析
使用场景:可重复使用的循环屏障; 类似:汽车站满员发班班车;
Semaphore 的原理分析及与线程池的区别
使用场景:限流
1、Condition 的使用及原理
案例: ConditionNotify 和 ConditionWait 实现一个notify 与wait的生产者-消费者效果;
ConditionWait
package com.hezm.thread.day1.tool;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class ConditionWait implements Runnable{
private Lock lock;
private Condition condition;
public ConditionWait(Lock lock, Condition condition) {
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
lock.lock(); //加锁
try {
System.out.println("begin- ConditionWait");
condition.await(); //阻塞
System.out.println("end- conditionWait");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock(); //释放锁
}
}
}
package com.hezm.thread.day1.tool;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class ConditionNotify implements Runnable{
private Lock lock;
private Condition condition;
public ConditionNotify(Lock lock, Condition condition) {
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
try {
lock.lock(); //加锁
System.out.println("begin- conditionNotify");
//唤醒一个等待在condition队列上的线程,将该线程转移到AQS同步队列中,
//如果在同步队列中能够竞争到Lock则可以从ConditionWait#wait()方法中返回。
condition.signal();
//ConditionNotify 中conditon 还要一个方法:
//signalAll(): 唤醒所有等待在condition队列上的线程
}finally {
System.out.println("end- conditionNotify");
lock.unlock(); //释放锁
System.out.println("unlock- conditionNotify");
}
}
}
ConditionNotify
package com.hezm.thread.day1.tool;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class ConditionNotify implements Runnable{
private Lock lock;
private Condition condition;
public ConditionNotify(Lock lock, Condition condition) {
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
try {
lock.lock(); //加锁
System.out.println("begin- conditionNotify");
//唤醒一个等待在condition队列上的线程,将该线程转移到AQS同步队列中,
//如果在同步队列中能够竞争到Lock则可以从ConditionWait#wait()方法中返回。
condition.signal();
//ConditionNotify 中conditon 还要一个方法:
//signalAll(): 唤醒所有等待在condition队列上的线程
}finally {
System.out.println("end- conditionNotify");
lock.unlock(); //释放锁
System.out.println("unlock- conditionNotify");
}
}
}
ConditionWait 中的wait方法:
1、void await() throws InterruptedException:当前线程进入等待状态,
唤醒机制:如果其他线程调用condition的signal或者signalAll方法并且当前线程获取Lock从await方法返回,
异常处理:如果在等待状态中被中断会抛出被中断异常;
2、long awaitNanos(long nanosTimeout):当前线程进入等待状态
唤醒机制:直到被通知,中断或者超时;
无异常处理;
3、boolean await(long time, TimeUnit unit)throws InterruptedException:同第二种,支持自定义时间单位
4、boolean awaitUntil(Date deadline) throws InterruptedException:当前线程进入等待状态
唤醒机制:直到被通知,或者到了某个时间
异常处理:如果在等待状态中被中断会抛出被中断异常;
主运行程序:
package com.hezm.thread.day1.tool;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionMain {
public static void main(String[] args) {
// 类似一个生产者消费者模型
Lock lock = new ReentrantLock(); //需要是一个重入锁;
Condition condition = lock.newCondition();
//两个线程必须是同一把锁和condition ,
new Thread( new ConditionWait(lock, condition)).start();
new Thread(new ConditionNotify(lock,condition)).start();
}
}
运行结果:
begin- ConditionWait
begin- conditionNotify
end- conditionNotify
end- conditionWait
unlock- conditionNotify或者
begin- ConditionWait
begin- conditionNotify
end- conditionNotify
unlock- conditionNotify
end- conditionWait结果解析:
这里似乎是在 ConditionNotify 执行了lock.unlock()才真正的释放锁; 否则为何一直执行都没有end- conditionNotify 在 end- conditionWait 之后执行的呢?
源码分析:
a,关键数据结构:
condition等待队列: 是通过AQS#Node来存储等待状态waitStatus和线程Thread;通过nextWaiter维护下一个Node实现的单向队列
ConditionObject是AQS的一个内部类,因此每个ConditionObject能够访问到AQS提供的方法,相当于每个Condition都拥有所属同步器的引用;
当我们多次调用lock.newCondition()的时候就创建了多个ConditionObject对象;具体结构如下:
AQS同步队列:taile和head 的AQS#Node存储节点状态waitStatus 和线程Thread,通过prev和next实现的双向队列;
其核心是状态管理; 实现了独占锁(acquire(int arg)、acquireInterruptibly(int arg) -可中断、tryAcquireNanos(int arg, long nanosTimeout) - 超时、 release(int arg) )、共享锁(acquireShared(int arg)、acquireSharedInterruptibly(int arg)-可中断、tryAcquireSharedNanos(int arg, long nanosTimeout)-超时、 releaseShared(int arg) )、可中断锁、超时等待锁等特性
waitStatus 核心状态值
int CANCELLED = 1//节点从同步队列中取消
int SIGNAL = -1//后继节点的线程处于等待状态,如果当前节点释放同步状态会通知后继节点,使得后继节点的线程能够运行;
int CONDITION = -2//当前节点进入等待队列中
int PROPAGATE = -3//表示下一次共享式同步状态获取将会无条件传播下去
int INITIAL = 0;//初始状态
b,Contion底层执行过程:
第一步ConditionWait先运行lock.lock();
获取到锁,Thread2会加入AQS队列,构建双向的头尾链表等待队列(null 为 head头 Thread2为尾 tail );
第二步执行 condition.await();AQS.await()
1,Thread1阻塞,加入Contion队列 :构建fistWaiters、 nextWaiters、 lastWaiters 三个节点指向的单向链表同步队列
新构建一个Node节点封装Thread1插入这个单向链表
2,fullyRelease() 执行释放锁 ,因为是重入锁,所以需要加fully进行全部释放;重入多少次就释放多少次;这里Thread2就会去抢占锁
3,!isOnSyncQueue(node) 当node不在AQS队列的时候将 Thread1 阻塞 LockSupport.park(this);
// 当先中断后 lock.signal() 的时候抛出异常, 先执行lock.signal() 再执行 interrupt() 重新发起中断;
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
第三步执行 ConditionNotify 执行lock.lock();
获取到锁,AQS队列的Thread2 进行执行状态,并且在AQS队列中成为 head头 同时后面没有新的Thread插入队列 tail也是Thread2
第四步 执行 lock.signal() ;调用 doSignal(fistWaiter)
获取 Condition 的 fistWaiters , transferForSignal(Node node)中 enq(node) 方法将 fistWaiters 添加到AQS队列中,返回 fistWaiters;
然后将 fistWaiters 从condition队列中移除掉;
LockSupport.unpark(node.thread); 唤醒Thread1;
{其他唤醒的时候:Thread.interrupt() 也会对线程进行唤醒 }
第五步 ConditionNotify 执行了lock.unlock(),真正的释放锁,公平锁唤醒head的下一个节点Thread1 将拿到锁继续执行;
非公平锁的时候用CAS方式争抢锁;
//第一步:lock.lock() ,最终调用:AbstractQueuedSynchronizer # acquire()
// 简单描述:抢占锁成功,执行当前线程,并初始化AQS队列同步器;
// 内部详细:参考 https://mp.csdn.net/postedit/98208145
public final void acquire(int arg) {
//
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//第二步:condition.await() 将该线程阻塞,并从AQS同步队列的头节点移除,尾插加入ContionObject等待队列;
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter(); //@1
int savedState = fullyRelease(node); //@2
int interruptMode = 0;
while (!isOnSyncQueue(node)) { //@3
LockSupport.park(this); /**阻塞:直至被signal/signalAll唤醒;*/
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) //@4
break;
}
// 自旋等待获取到同步状态(即获取到lock锁)
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // 被取消则清除
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
//addConditionWaiter(); @1 构建加入Contion队列的Node:构建fistWaiters、
//lastWaiters均指向Node{Thread1, nextWaiters = null} 的单向链表等待队列
// 新构建一个Node节点封装Thread1插入这个单向链表
//fullyRelease(node) @2 执行释放锁,因为是重入锁,所以需要加fully进行全部释放;重入多少次就释放多少次;
// 调用AQS的模板方法release方法释放AQS的同步状态并且唤醒在同步队列中头结点的后继节点引用的线程
//!isOnSyncQueue(node) //@3 当node不在AQS队列的时候调用LockSupport.park(this)
// 将Thread1 阻塞 ,在AQS队列则跳出循环;
//(interruptMode = checkInterruptWhileWaiting(node)) != 0 //@4 当程序中断则跳出循环
// 会去执行reportInterruptAfterWait(interruptMode);
// 情况1:interruptMode = -1 当先中断后 lock.signal() 的时候抛出异常,
// 情况2:interruptMode = 1 先执行lock.signal() 再执行 interrupt() 调用selfInterrupt();重新发起中断;
// 重新发起中断
//第三步执行 ConditionNotify 执行lock.lock();
// ConditionNotify线程获取到锁,AQS队列的Thread2进入执行状态,并且在AQS队列中成为 head头 同时后面没有新的Thread插入队列 tail也是Thread2
//第四步 执行 lock.signal() ;调用 doSignal(fistWaiter)
private void doSignal(Node first) { //@1
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null; //@2
} while (!transferForSignal(first) && //@3
(first = firstWaiter) != null); // @4
}
//Node first @1在singal()中获取的Condition等待队列的fistWaiters
// first.nextWaiter = null; //@2 将等待队列的头节点从等待队列中移除
// transferForSignal(Node node) //@3中 enq(node) 方法将头结点添加到AQS队列中,添加成功返回true;
// (first = firstWaiter) != null); // @4 等待队列下一个线程不为null则继续循环,直到为null
final boolean transferForSignal(Node node) {
//将Node的节点状态设置为初始化INIT
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//将头结点Node加入到AQS队列中,
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread); //当线程被取消或者修改线程状态为SIGNAL失败唤醒线程
return true;
}
//第五步 ConditionNotify 执行了lock.unlock(),真正的释放锁,公平锁唤醒head的下一个节点非公平锁的时候用CAS方式争抢锁;
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); //唤醒AQS同步队列head.next的Node中的线程
return true;
}
return false;
}
2、CountDownLatch 的原理分析
案例: CountDownLatchDemo 的两个案例
对应场景:模拟并发、控制等待前面线程任务执行完成再继续;
package com.hezm.thread.day1.tool;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo1 extends Thread{
//场景模拟并发场景
static CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new CountDownLatchDemo().start();
}
countDownLatch.countDown();
}
@Override
public void run() {
try {
countDownLatch.await(); //阻塞 共享锁用的 Thread.currentThread() 阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("ThreadName:" + Thread.currentThread().getName());
}
}
//场景2:并发控制,当前面程序执行完成之后再执行之后的逻辑
public class CountDownLatchDemo2 extends Thread{
public static void main(String[] args) throws InterruptedException {
CountDownLatch downLatch = new CountDownLatch(3);
new Thread(()->{
System.out.println("Thread1");
downLatch.countDown();
});
new Thread(()->{
System.out.println("Thread2");
downLatch.countDown();
});
new Thread(()->{
System.out.println("Thread3");
downLatch.countDown();
});
downLatch.await();
}
}
原理分析:
利用了AQS的可中断共享锁;实现多个线程在同一时间等待;
new CountDownLatch(1);中将数值设置给 state 作为计数器;
判断当前计数器 == 0 则表示可以被释放和唤醒。
核心方法:
- await() throws InterruptedException:调用该方法的线程等到构造方法传入的N减到0的时候,才能继续往下执行;
- await(long timeout, TimeUnit unit):与上面的await方法功能一致,只不过这里有了时间限制,调用该方法的线程等到指定的timeout时间后,不管N是否减至为0,都会继续往下执行;
- countDown():使CountDownLatch初始值N减1;
- long getCount():获取当前CountDownLatch维护的值;
源码分析:
/**
* 第一步:new CountDownLatch(1);中将数值设置给 state作为计数器; 判断当前计数器 == 0 则表示可以被释放和唤醒。
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
protected final void setState(int newState) {
state = newState; //设置线程状态值为 count中的数值;
}
//第二步:countDownLatch.await() 阻塞线程:
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED); // @1构建共享锁AQS同步队列 - Node.SHARED
boolean failed = true;
try {
for (;;) { //自旋
final Node p = node.predecessor(); //@2
if (p == head) { //@3 拿到当前的节点的前一个节点
int r = tryAcquireShared(arg); // 尝试竞争这个锁,计数器不为0 返回-1,0返回1;
if (r >= 0) {
setHeadAndPropagate(node, r); //唤醒所有线程
p.next = null; // 清除AQS同步队列所有子节点
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) && // 则阻塞队列所有线程
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//第三步:countDown()
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { //获取状态值是否修改为0
doReleaseShared(); //释放共享锁
return true;
}
return false;
}
protected boolean tryReleaseShared(int releases) {
// 自旋获取计算器为0的时候修改状态值
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
3、 CyclicBarrier 原理分析
使用案例:
使用场景:导入数据,当达到多少个导入进行一个批次的处理; 批量循环使用的线程控制工具类;
package com.hezm.thread.day1.tool;
import java.util.concurrent.CyclicBarrier;
/***
* 实现案例: 在不同的地方获取数据,在三个地方获取数据,都获取到之后才能进行计算;
*/
public class CycliBarrierDemo extends Thread{
@Override
public void run() {
System.out.println("开始进行数据分析");
}
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new CycliBarrierDemo());
new Thread(new ImportDataThread(cyclicBarrier,"path1")).start();
new Thread(new ImportDataThread(cyclicBarrier,"path2")).start();
new Thread(new ImportDataThread(cyclicBarrier,"path3")).start();
//以上完成才可以进行数据分析, 调用 CycliBarrierDemo.start();
}
}
package com.hezm.thread.day1.tool;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class ImportDataThread extends Thread {
private CyclicBarrier cyclicBarrier;
private String path;
public ImportDataThread(CyclicBarrier cyclicBarrier, String path) {
this.cyclicBarrier = cyclicBarrier;
this.path = path;
}
@Override
public void run() {
System.out.println("开始导入"+path+"路径的数据");
//todo
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
运行结果
开始导入path2路径的数据
开始导入path3路径的数据
开始导入path1路径的数据
开始进行数据分析
核心方法:
//等到所有的线程都到达指定的临界点
await() throws InterruptedException, BrokenBarrierException//与上面的await方法功能基本一致,只不过这里有超时限制,阻塞等待直至到达超时时间为止
await(long timeout, TimeUnit unit) throws InterruptedException,
BrokenBarrierException, TimeoutException//获取当前有多少个线程阻塞等待在临界点上
int getNumberWaiting()//用于查询阻塞等待的线程是否被中断
boolean isBroken()
//将屏障重置为初始状态。如果当前有线程正在临界点等待的话,将抛出BrokenBarrierException。
void reset()
源码分析:
//第一步: new CyclicBarrier(3, new CycliBarrierDemo())
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties; //记录线程数
this.barrierCommand = barrierAction; //在栅栏执行线程数完成的时候执行该action的 run()方法
}
//第二步: new Thread(new ImportDataThread(cyclicBarrier,"path1")).start(); 将锁要执行的线程包裹进新创建的线程池中;
//第三步:cyclicBarrier.await(); 将线程阻塞在这里,等待唤醒
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock(); //加锁
try {
final Generation g = generation;
if (g.broken)
throw new BrokenBarrierException();
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
int index = --count; //统计数-1
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run(); //执行CycliBarrierDemo的run()方法
ranAction = true;
nextGeneration(); //开启下一次栅栏
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
if (!timed)
trip.await(); //阻塞等待被唤醒
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
Thread.currentThread().interrupt();
}
}
if (g.broken)
throw new BrokenBarrierException();
if (g != generation)
return index;
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
//栅栏唤醒本轮栅栏线程, 初始化下一轮栅栏
private void nextGeneration() {
// signal completion of last generation
trip.signalAll();
// set up next generation
count = parties;
generation = new Generation();
}
4、Semaphore
使用案例:通过Semaphore实现小车的限流驶入和驶出
适用场景:限流,比如网关限制api并发访问量、数据库限制并发访问数
package com.hezm.thread.day1.tool;
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(5);
for (int i = 0; i < 10; i++) {
new Car(i+1, semaphore).start();
}
}
//定义车
static class Car extends Thread{
private int num;
private Semaphore semaphore;
public Car(int num, Semaphore semaphore) {
super();
this.num = num;
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire(); //获得一个令牌,拿不到令牌,就会阻塞;
System.out.println("小车" + num + "抢占一个车位");
Thread.sleep(1000);
System.out.println("小车" + num + "开走了");
/*** 释放锁 */
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//获取许可,如果无法获取到,则阻塞等待直至能够获取为止
void acquire() throws InterruptedException//同acquire方法功能基本一样,只不过该方法可以一次获取多个许可
void acquire(int permits) throws InterruptedException//释放许可
void release()//释放指定个数的许可
void release(int permits)//尝试获取许可,如果能够获取成功则立即返回true,否则,则返回false
boolean tryAcquire()//与tryAcquire方法一致,只不过这里可以指定获取多个许可
boolean tryAcquire(int permits)//尝试获取许可,如果能够立即获取到或者在指定时间内能够获取到,则返回true,否则返回false
boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException//与上一个方法一致,只不过这里能够获取多个许可
boolean tryAcquire(int permits, long timeout, TimeUnit unit)//返回当前可用的许可证个数
int availablePermits()//返回正在等待获取许可证的线程数
int getQueueLength()//是否有线程正在等待获取许可证
boolean hasQueuedThreads()//获取所有正在等待许可的线程集合
Collection<Thread> getQueuedThreads()
原理分析:
permits:令牌,5个令牌数则只能同时运行5个线程,当有的完成之后再可以处理排队的任务
锁竞争类型:有公平锁 和 非公平锁;
public Semaphore(int permits, boolean fair) { sync = fair ? new FairSync(permits) : new NonfairSync(permits); }
源码解析:
//第一步:new Semaphore(5); 初始化公平锁/非公平锁 设定令牌数量
protected final void setState(int newState) {
state = newState; //设定令牌数
}
//第二步:semaphore.acquire(); 获得令牌,获取不到令牌就会阻塞等待
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0) //要获取的令牌数 arg还够不够, < 0
doAcquireSharedInterruptibly(arg); //进入AQS的共享锁锁定
}
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED); // @1构建共享锁AQS同步队列 - Node.SHARED
boolean failed = true;
try {
for (;;) { //自旋
final Node p = node.predecessor(); //@2
if (p == head) { //@3 拿到当前的节点的前一个节点
int r = tryAcquireShared(arg); // 尝试竞争这个锁,计数器不为0 返回-1,0返回1;
if (r >= 0) {
setHeadAndPropagate(node, r); //唤醒所有线程
p.next = null; // 清除AQS同步队列所有子节点
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) && // 则阻塞队列所有线程
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//第三步: semaphore.release(); 释放令牌,唤醒阻塞等待的任务 可参考上面CountDownLatch 的案例
Seamphore和线程池的区别:
1、实际工作的线程是谁创建的?
使用线程池,实际工作线程由线程池创建;
使用Seamphore,实际工作的线程由你自己创建。
2、限流是否自动实现?
线程池自动;
Seamphore手动。
4、Exchanger
使用案例:通过Exchanger实现情书传送
适用场景:适用于线程间交换数据
public class ExchangerDemo {
private static Exchanger<String> exchanger = new Exchanger();
public static void main(String[] args) {
//代表男生和女生
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(() -> {
try {
//男生对女生说的话
String girl = exchanger.exchange("我其实暗恋你很久了......");
System.out.println("女孩儿说:" + girl);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
service.execute(() -> {
try {
System.out.println("女生慢慢的从教室你走出来......");
TimeUnit.SECONDS.sleep(3);
//男生对女生说的话
String boy = exchanger.exchange("我也很喜欢你......");
System.out.println("男孩儿说:" + boy);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
运行结果:
女生慢慢的从教室你走出来......
男孩儿说:我其实暗恋你很久了......
女孩儿说:我也很喜欢你......
主要方法:
//当一个线程执行该方法的时候,会等待另一个线程也执行该方法,因此两个线程就都达到了同步点
//将数据交换给另一个线程,同时返回获取的数据
V exchange(V x) throws InterruptedException//同上一个方法功能基本一样,只不过这个方法同步等待的时候,增加了超时时间
V exchange(V x, long timeout, TimeUnit unit)
throws InterruptedException, TimeoutException