死锁、ReentranLock(锁重入、锁超时、可中断、公平锁、条件变量)、同步模式之顺序控制
多把锁
- 小故事
- 一间大屋子有两个功能:
睡觉、学习,互不相干
。 - 现在小南要学习、小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么
并发度很低
- 小南获取锁之后,学完习之后,小女才能进来睡觉。
- 解决方法时准备多个房间(
多个对象锁
)
@Slf4j(topic = "guizy.BigRoomTest")
public class BigRoomTest {
public static void main(String[] args) {
BigRoom bigRoom = new BigRoom();
new Thread(() -> bigRoom.sleep(), "小南").start();
new Thread(() -> bigRoom.study(), "小女").start();
}
}
@Slf4j(topic = "guizy.BigRoom")
class BigRoom {
@SneakyThrows
public void sleep() {
synchronized (this) {
log.debug("sleeping 2 小时");
Thread.sleep(2);
}
}
@SneakyThrows
public void study() {
synchronized (this) {
log.debug("study 1 小时");
Thread.sleep(1);
}
}
}
// 相当于串行执行, 因为锁对象是整个屋子, 所以并发性很低
12:16:15.952 guizy.BigRoom [小南] - sleeping 2 小时
12:16:17.954 guizy.BigRoom [小女] - study 1 小时
- 改进让
小南, 小女
获取不同的锁即可
@Slf4j(topic = "guizy.BigRoomTest")
public class BigRoomTest2 {
private static final BigRoom sleepRoom = new BigRoom();
private static final BigRoom studyRoom = new BigRoom();
public static void main(String[] args) {
// 不同对象调用
new Thread(() -> sleepRoom.sleep(), "小南").start();
new Thread(() -> studyRoom.study(), "小女").start();
}
}
// 因为使用的是不同的锁对象
12:18:50.580 guizy.BigRoom [小女] - study 1 小时
12:18:50.580 guizy.BigRoom [小南] - sleeping 2 小时
- 将锁的粒度细分
- 好处,是容易
增加并发度
- 坏处,如果一个线程需要同时获得多把锁,就
容易发生死锁
- 好处,是容易
活跃性(死锁)
- 因为某种原因,是的代码一直无法执行完毕,这样的现象叫做 活跃性
- 活跃性相关的一系列问题都可以用 ReentranLock进行解决
死锁(重点
)
- 有这样的情况:一个线程需要 同时获得多把锁,这时候就容易发生死锁
如:线程1获取A对象锁,线程2获取B对象锁,此时线程1又想获取B对象锁,线程2又想获取A对象锁,它们都等着对象释放锁,此时就称为死锁
public class DeadLock {
public static void main(String[] args) {
final Object A = new Object();
final Object B = new Object();
new Thread(()->{
synchronized (A) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B) {
}
}
}).start();
new Thread(()->{
synchronized (B) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (A) {
}
}
}).start();
}
}
发生死锁的必要条件(重点
)
- 互斥条件
- 在一段时间内,
一种资源只能被一个进程锁使用
- 在一段时间内,
- 请求和保持条件
- 进程以及拥有至少一种资源,同时又去申请其他资源,因为其他资源被别的线程所使用,该进程进入阻塞状态,并且不释放自己已有的资源
- 不可抢占条件
- 进程对已获得的锁资源在未使用完成前不能被强占,只能在进程使用完后自己是否
- 循环等待条件
- 发生死锁时,必然存在一个进程 —资源的循环链
定位死锁的方法
方式一:JPS+JStack进程ID
- jps先找到JVM进程
- jstack 进程ID
- 在Java控制台中的
Terminal
中输入jps
指令可以查看正在运行的线程ID
,使用jstack 进程ID
可以查看进程状态
- 在Java控制台中的
方式二、 jconsole检测死锁
死锁举例 - 哲学家就餐问题 (重点
)
有五位哲学家,围坐在圆桌旁。
- 他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。
- 吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。
- 如果筷子被身边的人拿着,自己就得等待
当每个哲学家即线程持有一根筷子时,他们都在等待另一个线程释放锁,因此造成了死锁。
// 程序只执行了下面的打印, 没有停止, 没有打印
15:04:55.346 guizy.Philosopher [苏格拉底] - eating...
15:04:55.346 guizy.Philosopher [亚里士多德] - eating...
15:04:55.850 guizy.Philosopher [亚里士多德] - eating...
15:04:55.850 guizy.Philosopher [苏格拉底] - eating...
15:04:56.351 guizy.Philosopher [亚里士多德] - eating...
15:04:56.852 guizy.Philosopher [亚里士多德] - eating...
通过jps, jstack 进程id
查看死锁原因
Found one Java-level deadlock:
发现了一个Java级别的死锁
Found one Java-level deadlock:
=============================
"阿基米德":
waiting to lock monitor 0x000000001ae2a358 (object 0x00000000d6ea7420, a com.guizy.reentrantlock.Chopstick),
which is held by "苏格拉底"
"苏格拉底":
waiting to lock monitor 0x0000000017fb3518 (object 0x00000000d6ea7430, a com.guizy.reentrantlock.Chopstick),
which is held by "柏拉图"
"柏???图":
waiting to lock monitor 0x0000000017fb3468 (object 0x00000000d6ea7440, a com.guizy.reentrantlock.Chopstick),
which is held by "亚里士多德"
"亚里士多德":
waiting to lock monitor 0x0000000017fb0bd8 (object 0x00000000d6ea7450, a com.guizy.reentrantlock.Chopstick),
which is held by "赫拉克利特"
"赫拉克利特":
waiting to lock monitor 0x0000000017fb0c88 (object 0x00000000d6ea7460, a com.guizy.reentrantlock.Chopstick),
which is held by "阿基米德"
Java stack information for the threads listed above:
===================================================
"阿基米德":
at com.guizy.reentrantlock.Philosopher.run(PhilosopherEat.java:47)
- waiting to lock <0x00000000d6ea7420> (a com.guizy.reentrantlock.Chopstick)
- locked <0x00000000d6ea7460> (a com.guizy.reentrantlock.Chopstick)
"苏格拉底":
at com.guizy.reentrantlock.Philosopher.run(PhilosopherEat.java:47)
- waiting to lock <0x00000000d6ea7430> (a com.guizy.reentrantlock.Chopstick)
- locked <0x00000000d6ea7420> (a com.guizy.reentrantlock.Chopstick)
"柏拉图":
at com.guizy.reentrantlock.Philosopher.run(PhilosopherEat.java:47)
- waiting to lock <0x00000000d6ea7440> (a com.guizy.reentrantlock.Chopstick)
- locked <0x00000000d6ea7430> (a com.guizy.reentrantlock.Chopstick)
"亚里士多德":
at com.guizy.reentrantlock.Philosopher.run(PhilosopherEat.java:47)
- waiting to lock <0x00000000d6ea7450> (a com.guizy.reentrantlock.Chopstick)
- locked <0x00000000d6ea7440> (a com.guizy.reentrantlock.Chopstick)
"赫拉克利特":
at com.guizy.reentrantlock.Philosopher.run(PhilosopherEat.java:47)
- waiting to lock <0x00000000d6ea7460> (a com.guizy.reentrantlock.Chopstick)
- locked <0x00000000d6ea7450> (a com.guizy.reentrantlock.Chopstick)
Found 1 deadlock.
避免死锁的方法
- 在线程使用锁对象时,采用 固定加锁的顺序,可以使用Hash值的大小来确定加锁的先后
- 尽可能缩减加锁的范围,等到操作共享变量的时候才加锁
- 使用可释放的定时锁(一段时间申请不到锁的权限了,直接释放掉)
活锁
活锁
出现在两个线程互相改变对方的结束条件
,谁也无法结束
避免活锁的方法
- 在线程执行时,中途给予
不同的间隔时间
,让某个线程先结束计即可。
死锁与活锁的区别
- 死锁时因为线程互相持有对象想要的锁,并且都不释放锁,最后到时 线程阻塞,停止运行的现象。
- 活锁是因为线程间修改了对方的结束条件,而导致代码 一直在运行,却一直 运行不完的现象。
饥饿
- 某些线程因为优先级太低,导致一直无法获得资源的现象。(finally线程)
- 在使用
顺序加锁
时,可能会出现饥饿现象
ReentrantLock (重点)
ReentranLock
的特点(synchronize不具备的)
-
支持锁重入
- 可重入锁是指
同一个线程如果首次获得了锁
,那么因为它是这把锁的拥有者
,因此 有权利再次获得这把锁
- 可重入锁是指
-
可中断
lock.lockInterruptibly():
可以被其他线程打断的中断锁
-
可以设置超时时间
lock.tryLock(time)
:尝试获取锁对象,如果超过了设计的时间,还没有获取到锁,此时就退出阻塞队列,并释放自己拥有的锁
-
可以设置为公平锁
- (先到先得)
默认是非公平,true为公平 new ReentranLock(true)
- (先到先得)
-
支持多个条件变量(
有多个waitset
)- (可避免虚假唤醒)–lock.newCondition()创建条件变量对象;通过条件变量对象调用await/singal方法,等待/唤醒
ReentrantLock特点
支持锁重入
- 可重入锁是指
同一个线程如果首次获得了这把锁
,那么因为它是这把锁的拥有者
,因此 有权利再次获取这把锁 - 如果是不可重入锁,那么第二次获取锁时,自己也会被锁住
DEBUG [main] - entry main...
DEBUG [main] - entry m1...
DEBUG [main] - entry m2....
可中断(针对于lockInterruptibly()方法获得的中断锁)直接退出阻塞队列,获取锁失败
synchronize
和reentranlock.lock()
的锁,是不可被打断的,也就是说别的现场已经获得了锁,我的线程就需要一直等待下去不能中断
- 可被中断的锁,通过
lock.lockInterruptibly()
获取的锁对象,可以通过 阻塞线程的interrupt()方法
- 如果
某个线程处于阻塞状态
,可以调用其interrupt方法
让其停止阻塞
, 获取锁失败- 处于阻塞状态的线程,被打断了就不用阻塞了,直接停止运行
- 可中断的锁,在一定程度上可以
被动
的减少死锁
的概率,之所以被动,是因为我们需要手动调用阻塞线程的interrupt方法
测试可以使用lock.lockInterruptibly()
可以从阻塞队列中打断。
14:18:09.145 guizy.ReentrantTest [main] - main线程获得了锁
14:18:09.148 guizy.ReentrantTest [t1] - t1线程启动...
14:18:10.149 guizy.ReentrantTest [main] - 执行打断
14:18:10.149 guizy.ReentrantTest [t1] - 等锁的过程中被打断
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at com.guizy.reentrantlock.ReentrantTest.lambda$main$0(ReentrantTest.java:25)
at java.lang.Thread.run(Thread.java:748)
测试使用lock.lock()
不可以从阻塞队列中打断, 一直等待别的线程释放锁
14:21:01.329 guizy.ReentrantTest [main] - main线程获得了锁
14:21:01.331 guizy.ReentrantTest [t1] - t1线程启动...
14:21:05.333 guizy.ReentrantTest [main] - main线程执行打断
14:21:05.333 guizy.ReentrantTest [t1] - t1线程获得了锁
锁超时(lock,tryLock()) 直接退出阻塞队列,获取锁失败
防止
无限制
等待,减少死锁
- 使用
lock.trylock()
方法会返回获取锁是否成功。
成功成功则返回true,反之则返回fasle。 - 并且
tryLock方法
可以设置 **指定等待时间,**参数为:tryLock(long timeout,TimeUnit unit)
,其中timeout为最长等待时间,TimeUnit为时间单位
获取锁的过程中,如果
超过等待时间
,或者被打断
,就直接从阻塞队列
移除,此时获取锁就失败了,不会一直阻塞着!(可以用来实现死锁问题)
- 不设置等待时间, 立即失败
@Slf4j(topic = "guizy.ReentrantTest")
public class ReentrantTest4 {
private static final ReentrantLock lock = new ReentrantLock();
@SneakyThrows
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("尝试获得锁");
// 此时肯定获取失败, 因为主线程已经获得了锁对象
if (!lock.tryLock()) {
log.debug("获取立刻失败,返回");
return;
}
try {
log.debug("获得到锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得到锁");
t1.start();
// 主线程2s之后才释放锁
Thread.sleep(2);
log.debug("释放了锁");
lock.unlock();
}
}
代码结果如下:
14:52:19.726 guizy.WaitNotifyTest [main] - 获得到锁
14:52:19.728 guizy.WaitNotifyTest [t1] - 尝试获得锁
14:52:19.728 guizy.WaitNotifyTest [t1] - 获取立刻失败,返回
14:52:21.728 guizy.WaitNotifyTest [main] - 释放了锁
- 设置等待时间, 超过等待时间还没有获得锁, 失败, 从阻塞队列移除该线程
@Slf4j(topic = "guizy.ReentrantTest")
public class ReentrantTest5 {
private static final ReentrantLock lock = new ReentrantLock();
@SneakyThrows
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("尝试获得锁");
try {
// 设置等待时间, 超过等待时间 / 被打断, 都会获取锁失败; 退出阻塞队列
if (!lock.tryLock(1, TimeUnit.SECONDS)) {
log.debug("获取锁超时,返回");
return;
}
} catch (InterruptedException e) {
log.debug("被打断了, 获取锁失败, 返回");
e.printStackTrace();
return;
}
try {
log.debug("获得到锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得到锁");
t1.start();
// t1.interrupt();
// 主线程2s之后才释放锁
Thread.sleep(2);
log.debug("main线程释放了锁");
lock.unlock();
}
}
运行结果如下:
DEBUG [main] - 获得到锁
DEBUG [t1] - 尝试获得锁
DEBUG [main] - main线程释放了锁
DEBUG [t1] - 获得到锁
// 中断的打印
14:56:41.258 guizy.WaitNotifyTest [main] - 获得到锁
14:56:41.260 guizy.WaitNotifyTest [main] - main线程释放了锁
14:56:41.261 guizy.WaitNotifyTest [t1] - 尝试获得锁
14:56:41.261 guizy.WaitNotifyTest [t1] - 被打断了, 获取锁失败, 返回
通过lock.tryLock()
来解决,哲学家就餐
问题(重点
)
lock.tryLock(时间):
尝试获取锁对象,如果超过了设置时间,还没有获取到锁,此时就退出阻塞队列,并释放掉自己拥有的锁
package com.yc.test2;
/**
* @author HillCheung
* @version 1.0
* @date 2021/4/12 19:05
*/
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* Description: ReentrantLock, 演示RenntrantLock中的tryLock(long mills), 超过锁设置的等待时间,就从阻塞队列移除
*
* @author guizy1
* @date 2020/12/23 13:50
*/
@Slf4j(topic = "guizy.ReentrantTest")
public class ReentrantTest5 {
private static final ReentrantLock lock = new ReentrantLock();
@SneakyThrows
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("尝试获得锁");
try {
// 设置等待时间, 超过等待时间 / 被打断, 都会获取锁失败; 退出阻塞队列
if (!lock.tryLock(1, TimeUnit.SECONDS)) {
log.debug("获取锁超时,返回");
return;
}
} catch (InterruptedException e) {
log.debug("被打断了, 获取锁失败, 返回");
e.printStackTrace();
return;
}
try {
log.debug("获得到锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得到锁");
t1.start();
// t1.interrupt();
// 主线程2s之后才释放锁
Thread.sleep(2);
log.debug("main线程释放了锁");
lock.unlock();
}
}
DEBUG [苏格拉底] - eating...
DEBUG [亚里士多德] - eating...
DEBUG [苏格拉底] - eating...
DEBUG [亚里士多德] - eating...
DEBUG [苏格拉底] - eating...
DEBUG [亚里士多德] - eating...
DEBUG [苏格拉底] - eating...
DEBUG [亚里士多德] - eating...
DEBUG [苏格拉底] - eating...
DEBUG [亚里士多德] - eating...
DEBUG [苏格拉底] - eating...
DEBUG [亚里士多德] - eating...
DEBUG [苏格拉底] - eating...
DEBUG [亚里士多德] - eating...
DEBUG [苏格拉底] - eating...
DEBUG [亚里士多德] - eating...
DEBUG [苏格拉底] - eating...
DEBUG [亚里士多德] - eating...
DEBUG [苏格拉底] - eating...
DEBUG [亚里士多德] - eating...
DEBUG [苏格拉底] - eating...
DEBUG [亚里士多德] - eating...
DEBUG [苏格拉底] - eating...
DEBUG [亚里士多德] - eating...
DEBUG [苏格拉底] - eating...
DEBUG [亚里士多德] - eating...
DEBUG [苏格拉底] - eating...
DEBUG [亚里士多德] - eating...
DEBUG [苏格拉底] - eating...
DEBUG [亚里士多德] - eating...
DEBUG [苏格拉底] - eating...
DEBUG [亚里士多德] - eating...
DEBUG [苏格拉底] - eating...
DEBUG [亚里士多德] - eating...
DEBUG [苏格拉底] - eating...
DEBUG [亚里士多德] - eating...
DEBUG [苏格拉底] - eating...
DEBUG [亚里士多德] - eating...
DEBUG [苏格拉底] - eating...
公平锁 new ReentranLock(true)
ReentranLock默认是非公平锁,可以指定为公平锁。
- 在线程获取锁失败,进入阻塞队列时, 先进入的会在锁被释放后 先获得锁。这样的获取方式就是 公平的。一般不设置
ReentranLock
为公平,会降低并发性
Synchronize
底层的Monitor锁
就是不公平的,和谁先进入阻塞队列
是没有关系的。
//默认是不公平锁,需要在创建时指定为公平锁
ReentrantLock lock = new ReentrantLock(true);
什么是公平锁?什么是非公平锁?
公平锁(new ReentranLock(true))
- 公平锁,可以把竞争的线程放在一个先进先出的阻塞队列上
- 只要持有锁的线程执行完了,唤醒阻塞队列中的下一个线程获取锁即可,此时先进入阻塞队列的线程先获取锁
非公平锁(synchronize,new ReentranLock())
- 非公平锁,当阻塞队列中已经有等待的线程A了,此时后到的线程B,先去尝试看能否得到锁对象,如果获取成功,此时就不需要进去阻塞队列了,这样一来后来的线程B就先得到锁了
所以公平和非公平的区别:
线程执行同步代码块的时候,是否回去尝试获取锁,
如有会尝试获取锁,那么就是非公平的,如果不会尝试获取锁,直接进入阻塞队列,在等待被唤醒,那就是公平的
- 如果不进入队列呢?线程一直尝试获取锁不就行了?
- 一直尝试获取锁,在Synchronize轻量级锁升级为重量级锁时,做的一个优化 ,叫做
自旋锁
, 一般很消耗资源,cpu一直空转,最后获取锁也失败,所以不推荐使用。在Jdk6对于自旋锁有一个机制,在重试获取锁指定次数就失败等等
条件变量(可避免虚假唤醒) -lock.newCondition()创建条件变量对象;通过条件变量对象调用 await/signal
方法,等待/唤醒
synchronize
中也有条件变量
,就是Monitor监视器
中的waitSet等待集合
,当条件不满足时进入waitSet等待
ReentranLock
的条件变量比synchronize强大之处在于,它是支持多个条件变量。
- 这就好比synchronize是哪些不满足条件的线程都在
一间休息室
等通知;(此时会造成虚假唤醒)
,而ReentranLock支持多间休息室
,有专门等烟的休息室、专门等造成的休息室、唤醒时也是按休息室来唤醒;(可以避免虚假唤醒)
使用要点:
- await前需要 获得锁
- await执行后,会释放锁,进入
conditionObject
(条件变量)中等待 - await的线程被唤醒(或打断、或超时)取重新竞争lock锁
- 竞争lock锁成功后,从await后继续执行
- signal方法用来唤醒
条件变量(等待室)
汇总的某一个等待的线程 - signalAll方法,唤醒
条件变量(休息室)
中的所有线程
DEBUG [小南] - 有烟没?[false]
DEBUG [小南] - 没烟,先歇会!
DEBUG [小女] - 外卖送到没?
DEBUG [小女] - 没外卖,先歇会!
DEBUG [送外卖的] - 送外卖的来了~~
DEBUG [小女] - 外面来了,可以开始干活了!
DEBUG [送烟的] - 送烟的来咯~
同步模式之顺序控制(案例)
- 假设有两个线程,线程A打印1,线程B打印2
- 要求: 程序先打印2,再打印1
1、Wait/Notify版本实现
@Slf4j
public class SyncPrintWaitTest {
public static final Object lock =new Object();
//t2线程释放执行过
public static boolean t2Runned =false;
public static void main(String[] args) {
Thread t1 =new Thread(()->{
synchronized (lock){
while(!t2Runned){
try {
//进入等待(waitSet),会释放锁
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
}
}
}
log.debug("1");
},"t1");
Thread t2 =new Thread(()->{
synchronized (lock){
log.debug("2");
t2Runned=true;
lock.notifyAll();
}
},"t2");
t1.start();
t2.start();
}
}
2、使用ReentrantLock的await/signal
public class SyncPrintWaitTest2 {
public static final ReentrantLock lock =new ReentrantLock();;
public static Condition condition =lock.newCondition();
//t2线程释放执行过
public static boolean t2Runned =false;
public static void main(String[] args) {
Runnable target;
Thread t1 =new Thread(() ->{
lock.lock();
try {
//临界区
while(!t2Runned){
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("1");
}finally {
lock.unlock();
}
},"t1");
Thread t2 =new Thread(()->{
lock.lock();
try {
log.debug("2");
t2Runned=true;
condition.signal();
}finally {
lock.unlock();
}
},"t2");
t1.start();
t2.start();
}
}
public class SyncPrintWaitTest3 {
public static void main(String[] args) {
Runnable target;
Thread t1 =new Thread(()->{
LockSupport.park();
log.debug("1");
},"t1");
t1.start();
new Thread(()->{
log.debug("2");
LockSupport.unpark(t1);
},"t2").start();
}
}
交替输出
需求
- 线程1 输出 a 5次, 线程2 输出 b 5次, 线程3 输出 c 5次。现在要求输出 abcabcabcabcabcabc
wait/notify版本
@Slf4j(topic = "TestWaitNotify")
public class TestWaitNotify {
public static void main(String[] args) {
WaitNotify waitNotify =new WaitNotify(1,5);
new Thread(()->{
waitNotify.print("a",1,2);
},"a线程").start();
new Thread(()->{
waitNotify.print("b",2,3);
},"b线程").start();
new Thread(()->{
waitNotify.print("c",3,1);
},"c线程").start();
}
}
@Data
@AllArgsConstructor
@Slf4j(topic = "WaitNotify")
class WaitNotify{
private int flag;
//循环次数
private int loopNumber;
public void print(String str,int waitFlag,int nextFlag){
for (int i = 0; i <loopNumber ; i++) {
synchronized (this){
while(waitFlag!=this.flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(str);
this.flag=nextFlag;
this.notifyAll();
}
}
}
}
2、await/signal版本
package com.yc.test3;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author HillCheung
* @version 1.0
* @date 2021/4/12 21:24
*/
@Slf4j
public class TestAwaitSignal {
public static void main(String[] args) {
AwaitSigal awaitSigal =new AwaitSigal(5);
Condition condition_a = awaitSigal.newCondition();
Condition condition_b = awaitSigal.newCondition();
Condition condition_c = awaitSigal.newCondition();
new Thread(()->{
awaitSigal.print("a",condition_a,condition_b);
},"a").start();
new Thread(()->{
awaitSigal.print("b",condition_b,condition_c);
},"b").start();
new Thread(()->{
awaitSigal.print("c",condition_c,condition_a);
},"c").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开始=======");
awaitSigal.lock();
try {
condition_a.signal(); //先唤醒a线程
}finally {
awaitSigal.unlock();
}
}
}
class AwaitSigal extends ReentrantLock{
private final int loopNumber;
AwaitSigal(int loopNumber) {
this.loopNumber = loopNumber;
}
public void print(String str, Condition condition,Condition next){
for (int i = 0; i < loopNumber; i++) {
lock();
try {
try {
condition.await(); //睡眠
System.out.print(str);
next.signal(); //唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
unlock();
}
}
}
}
3、LockSupport的park/unpark实现
package com.yc.test3;
/**
* @author HillCheung
* @version 1.0
* @date 2021/4/13 10:49
*/
/**
* Description: 使用park/unpark来实现三个线程交替打印abcabcabcabcabc
*
* @author guizy1
* @date 2020/12/23 17:12
*/
@Slf4j(topic = "guizy.TestWaitNotify")
public class TestParkUnpark {
static Thread a;
static Thread b;
static Thread c;
public static void main(String[] args) {
ParkUnpark parkUnpark = new ParkUnpark(5);
a = new Thread(() -> {
parkUnpark.print("a", b);
}, "a");
b = new Thread(() -> {
parkUnpark.print("b", c);
}, "b");
c = new Thread(() -> {
parkUnpark.print("c", a);
}, "c");
a.start();
b.start();
c.start();
LockSupport.unpark(a);
}
}
class ParkUnpark {
private final int loopNumber;
public ParkUnpark(int loopNumber) {
this.loopNumber = loopNumber;
}
public void print(String str, Thread nextThread) {
for (int i = 0; i < loopNumber; i++) {
LockSupport.park();
System.out.print(str);
LockSupport.unpark(nextThread);
}
}
}