死锁
要点
一个线程需要同时获取多把锁,这是就容易发生死锁,例如:
- t1线程获得A对象的锁,接下来想获得B对象的锁
- t2线程获得B对象的锁,接下来想获得A对象的锁
代码示例
public class DeathLockDemo {
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
Runnable task1= () -> {
synchronized (a){
log.info("lock a...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b){
log.info("lock b...");
log.info("do sth.");
}
}
};
Runnable task2= () -> {
synchronized (b){
log.info("lock b...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a){
log.info("lock a...");
log.info("do sth.");
}
}
};
new Thread(task1,"t1").start();
new Thread(task2,"t2").start();
}
}
打印日志如下,线程t1与线程t2均处于卡死状态,无法往下运行。
[299 ms] [INFO][t2] i.k.e.c.e.DeathLockDemo : lock b...
[299 ms] [INFO][t1] i.k.e.c.e.DeathLockDemo : lock a...
用jps和定位死锁
具体步骤:
- 运行上述代码
- 打开控制台/终端,输入
jps
查看系统中的java进程号
我电脑中的打印如下,其中死锁程序对应的进程号为16876
6692 Launcher
9252 Jps
4056 RemoteMavenServer
7864 KotlinCompileDaemon
16876 DeathLockDemo
4492 Launcher
- 在控制台/终端,输入
jstack 进程号
可以查看java进程的栈信息,如果程序中存在死锁,相关信息也会打印出来
我电脑中,死锁相关信息打印如下,上半部分包含了死锁的线程与原因,下半部分则展示了死锁线程的信息与发生的位置
Found one Java-level deadlock:
=============================
"t1":
waiting to lock monitor 0x000001c93f2aa500 (object 0x000000071156ec58, a java.lang.Object),
which is held by "t2"
"t2":
waiting to lock monitor 0x000001c93f2aad00 (object 0x000000071156ec48, a java.lang.Object),
which is held by "t1"
Java stack information for the threads listed above:
===================================================
"t1":
at info.kuangkuang.exercise.concurrent.ex.DeathLockDemo.lambda$main$0(DeathLockDemo.java:25)
- waiting to lock <0x000000071156ec58> (a java.lang.Object)
- locked <0x000000071156ec48> (a java.lang.Object)
at info.kuangkuang.exercise.concurrent.ex.DeathLockDemo$$Lambda$35/0x000000080121c040.run(Unknown Source)
at java.lang.Thread.run(java.base@12/Thread.java:835)
"t2":
at info.kuangkuang.exercise.concurrent.ex.DeathLockDemo.lambda$main$1(DeathLockDemo.java:40)
- waiting to lock <0x000000071156ec48> (a java.lang.Object)
- locked <0x000000071156ec58> (a java.lang.Object)
at info.kuangkuang.exercise.concurrent.ex.DeathLockDemo$$Lambda$36/0x000000080121c440.run(Unknown Source)
at java.lang.Thread.run(java.base@12/Thread.java:835)
Found 1 deadlock.
哲学家就餐问题
有N位哲学家,围坐在圆桌旁。
- 他们只做两件事情:思考与吃饭,思考一会吃一口饭,吃完后节则思考
- 吃饭需要使用两根筷子,桌上一共有N根筷子,每位哲学家左手右手各有一根
- 如果筷子被边上的人拿走,自己就得等待
注意 :此时如果每个哲学家都持有一根筷子,那么谁都吃不了饭,谁也不会放下筷子
下面用代码对该问题进行演示
筷子类
筷子类仅包含一个名字用以区分不同的筷子
class Chopstick{
private String name;
public Chopstick(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
哲学家类
哲学家类组合了两个筷子对象,分别用来表示左手筷子和右手筷子。
哲学家类继承了Thead
类,并修改了其构造方法和run()
方法,在run()
方法,哲学家需要同时持有两个对象的锁才会执行吃饭函数eat()
。吃饭函数eat()
会在吃饭前后打印日志,
class Philosopher extends Thread{
private final Chopstick left;
private final Chopstick right;
public Philosopher(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
@Override
public void run() {
while (true) {
synchronized (left) {
synchronized (right) {
eat();
}
}
}
}
private void eat(){
log.info("开始吃饭...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("饭吃完了");
}
}
测试类
在测试类中,我实例化了N个筷子类和N个哲学家类,并讲哲学家对象与筷子对象进行了关联。
我将变量N取为3,方便快速看到死锁效果。
public class PhilosopherEating {
public static void main(String[] args) {
int N = 3;
Chopstick[] chopsticks = new Chopstick[N];
for (int i = 0; i < N; i++) {
chopsticks[i] = new Chopstick("筷子-"+i);
}
Philosopher[] philosophers = new Philosopher[N];
for (int i = 0; i < N; i++) {
philosophers[i] = new Philosopher("哲学家-"+i,chopsticks[i],chopsticks[(i+1)%N]);
}
for (int i = 0; i < N; i++) {
philosophers[i].start();
}
}
}
打印日志
测试类打印日志如下:当程序运行了约6秒后,不再有打印日志,初步判断程序陷入了死锁,具体可以通过jstack工具分析判断。
[173 ms] [INFO][哲学家-0] i.k.e.c.h.Philosopher : 开始吃饭...
[1187 ms] [INFO][哲学家-0] i.k.e.c.h.Philosopher : 饭吃完了
[1187 ms] [INFO][哲学家-2] i.k.e.c.h.Philosopher : 开始吃饭...
[2199 ms] [INFO][哲学家-2] i.k.e.c.h.Philosopher : 饭吃完了
[2199 ms] [INFO][哲学家-2] i.k.e.c.h.Philosopher : 开始吃饭...
[3214 ms] [INFO][哲学家-2] i.k.e.c.h.Philosopher : 饭吃完了
[3214 ms] [INFO][哲学家-2] i.k.e.c.h.Philosopher : 开始吃饭...
[4228 ms] [INFO][哲学家-2] i.k.e.c.h.Philosopher : 饭吃完了
[4228 ms] [INFO][哲学家-1] i.k.e.c.h.Philosopher : 开始吃饭...
[5242 ms] [INFO][哲学家-1] i.k.e.c.h.Philosopher : 饭吃完了
[5242 ms] [INFO][哲学家-1] i.k.e.c.h.Philosopher : 开始吃饭...
[6255 ms] [INFO][哲学家-1] i.k.e.c.h.Philosopher : 饭吃完了
jstack分析结果
jstack分析结果如下,哲学家-0,1,2相互之间持有了其他人需要的锁,导致程序无法往下进行,形成了死锁。
Found one Java-level deadlock:
=============================
"哲学家-0":
waiting to lock monitor 0x000001c6c99cb200 (object 0x0000000711cf9010, a info.kuangkuang.exercise.concurrent.homework.Chopstick),
which is held by "哲学家-1"
"哲学家-1":
waiting to lock monitor 0x000001c6c99cb100 (object 0x0000000711cf90d0, a info.kuangkuang.exercise.concurrent.homework.Chopstick),
which is held by "哲学家-2"
"哲学家-2":
waiting to lock monitor 0x000001c6c99c9200 (object 0x0000000711cf8df8, a info.kuangkuang.exercise.concurrent.homework.Chopstick),
which is held by "哲学家-0"
解决办法
- 按照固定的顺序加载锁,即不同类之间加载锁的先后顺序应该是一样的
- 引入可重入锁
ReentrantLock
,后续会介绍到
活锁
要点
活锁指两个线程互相改变对方的结束条件,使得最后谁也无法结束
代码示例
在示例代码中,变量i
的初始值为10。subTask
需要将变量i
减到0才能结束任务,而addTask
需要将变量i
加到20才能结束任务。如果两个代码同时执行,则会让变量i
不断地在10附近浮动,进而导致两个任务都难以结束。
public class LiveLockDemo {
public static int i = 10;
public static void main(String[] args) {
Runnable subTask = () -> {
while (i > 0){
i --;
log.info("i = {}",i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Runnable addTask = () -> {
while (i < 20){
i ++;
log.info("i = {}",i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
new Thread(subTask,"sub").start();
new Thread(addTask,"add").start();
}
}
打印日志如下,变量i
总是在10附近浮动,两个线程难以结束。
[163 ms] [INFO][add] i.k.e.c.e.LiveLockDemo : i = 10
[163 ms] [INFO][sub] i.k.e.c.e.LiveLockDemo : i = 9
[380 ms] [INFO][sub] i.k.e.c.e.LiveLockDemo : i = 9
[380 ms] [INFO][add] i.k.e.c.e.LiveLockDemo : i = 10
[590 ms] [INFO][add] i.k.e.c.e.LiveLockDemo : i = 11
[590 ms] [INFO][sub] i.k.e.c.e.LiveLockDemo : i = 10
[799 ms] [INFO][add] i.k.e.c.e.LiveLockDemo : i = 11
[799 ms] [INFO][sub] i.k.e.c.e.LiveLockDemo : i = 10
[1001 ms] [INFO][add] i.k.e.c.e.LiveLockDemo : i = 11
[1001 ms] [INFO][sub] i.k.e.c.e.LiveLockDemo : i = 10
[1205 ms] [INFO][sub] i.k.e.c.e.LiveLockDemo : i = 9
[1205 ms] [INFO][add] i.k.e.c.e.LiveLockDemo : i = 10
[1409 ms] [INFO][add] i.k.e.c.e.LiveLockDemo : i = 10
ReentrantLock
可重入锁
可重入是指:同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获得这把锁。
如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住。
相比synchronized
它具备一下特点
- 可中断
- 可以设置超时时间
- 可以设置为公平锁(防止线程饥饿)
- 支持多个条件变量(可以对等待的线程进行细分)
synchronized
和ReentrantLock
都是可重入锁。
基本语法与使用规范
- 创建一个
ReentrantLock
对象 - 调用
ReentrantLock
对象的lock()
方法 - 将临界区的代码写在try代码块中
- 将
ReentrantLock
对象的unlock()
方法写在finally代码块中
注意:lock.lock();
与try代码块之间不要有空行或者其它逻辑,且lock.unlock();
要写在finally代码块的第一行。
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
}finally {
lock.unlock();
}
}
特点1:可重入
public class ReentrantLockDemo {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
new Thread(() -> method1(),"t").start();
method1();
}
public static void method1(){
lock.lock();
try {
log.info("method 1...");
method2();
}finally {
lock.unlock();
}
}
private static void method2() {
lock.lock();
try {
log.info("method 2...");
}finally {
lock.unlock();
}
}
}
打印日志如下,主函数和线程t都成功执行了方法1和方法2
[160 ms] [INFO][main] i.k.e.c.e.ReentrantLockDemo : method 1...
[162 ms] [INFO][main] i.k.e.c.e.ReentrantLockDemo : method 2...
[163 ms] [INFO][t] i.k.e.c.e.ReentrantLockDemo : method 1...
[163 ms] [INFO][t] i.k.e.c.e.ReentrantLockDemo : method 2...
这里需要注意:加锁与解锁是必须匹配的,只有当解锁次数==加锁次数时,锁才会被正确释放。
特点2:可打断
lock.lock()
等待锁是不可以被打断的lock.lockInterruptibly()
的等待过程是可以被打断的
代码示例:主线程首先获得锁,因此被创建出的线程t1启动后无法获得锁,之后,主线程打断线程t1,使得线程t1结束等待。
public class ReentrantLockDemo2 {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
lock.lock();
try {
Thread t1 = new Thread(() -> method1(), "t1");
t1.start();
t1.interrupt();
}finally {
lock.unlock();
}
}
public static void method1(){
try {
lock.lockInterruptibly();
try {
log.info("method 1...");
}finally {
lock.unlock();
}
} catch (InterruptedException e) {
log.info("被打断,没有获得锁");
}
}
}
打印日志如下:
[162 ms] [INFO][t1] i.k.e.c.e.ReentrantLockDemo2 : 被打断,没有获得锁
特点3:锁超时
当等待锁的时间超过给定时间时,会触发锁超时,不再等待。
- 可打断属于被动避免一直等待
- 可打断属于主动避免一直等待
锁超时需要使用lock.trylock()
方法,相关API有两个:
- 如果无参数传入,则会立刻返回尝试获取锁的结果;
- 也可以使用
tryLock(long timeout, TimeUnit unit)
传入最大等待时间,如果在等待时间内获取到锁则立刻返回true
,如果等待超时则返回false
。
@Slf4j(topic = "kzz")
public class ReentrantLockDemo3 {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
lock.lock();
try {
Thread t1 = new Thread(() -> method1(), "t1");
t1.start();
Thread.sleep(200);
}finally {
lock.unlock();
}
}
public static void method1(){
log.info("开始等待锁");
try {
if (!lock.tryLock(100, TimeUnit.MILLISECONDS)){
log.info("等待超时,没有获得锁");
return;
}
} catch (InterruptedException e) {
log.info("被打断,没有获得锁");
e.printStackTrace();
return;
}
try {
log.info("获得到锁");
}finally {
lock.unlock();
}
}
}
打印日志如下,可以看到线程t1大约等待100ms后出现超时日志
[165 ms] [INFO][t1] i.k.e.c.e.ReentrantLockDemo3 : 开始等待锁
[277 ms] [INFO][t1] i.k.e.c.e.ReentrantLockDemo3 : 等待超时,没有获得锁
如果把主线程中的睡眠时间改为50ms,打印日志如下,可以看到线程t1大约等待50ms后就获得了锁
[173 ms] [INFO][t1] i.k.e.c.e.ReentrantLockDemo3 : 开始等待锁
[235 ms] [INFO][t1] i.k.e.c.e.ReentrantLockDemo3 : 获得到锁
特点4:公平锁
公平锁是指:按照阻塞队列的顺序,先到先得的获得锁。
ReentrantLock
默认为非公平锁。
可以通过new ReentrantLock(true)
方式将其设置为公平锁。
公平锁一般没有必要,会降低并发度。
条件变量
关键字synchronized
中也有条件变量,就是waitSet
,可以理解为条件不满足时进入waitSet
等待,一个synchronized
只能对应一个waitSet
。
而ReentrantLock
可以支持多个条件变量,因此可以将不同条件的线程放入等待集合中,以便于后续进行专门的唤醒。
在ReentrantLock
中使用条件变量需要使用await()
方法
使用流程
- 使用
ReentrantLock
对象创建条件变量condition
; - 执行
condition.await()
前需要先获取锁; - 执行
condition.await()
后,线程会释放锁,并进入conditionObject
中等待; - 其它线程执行````condition.signal()
或者
condition.signalAll()唤醒
conditionObject```中等待的线程; - 被唤醒后会重新竞争锁
- 竞争锁成功后,会从
await()
后的代码处开始执行
函数await()
包含多种调用方式,具体如下:
函数签名 | 用法 | 返回值含义 |
---|---|---|
void await() | 进入等待,直到被其它线程唤醒 | 无返回值 |
boolean await(long time, TimeUnit unit) | 进入等待,直到被其它线程唤醒或超时 | 如果等待时间耗尽依然没有被唤醒,则会返回false |
void awaitUninterruptibly() | 该次等待不可以被打断,其余都是可以被打断的 | 无返回值 |
boolean awaitUntil(Date deadline) | 进入等待,直到被其它线程唤醒或到达某一时间节点 | 如果到达指定时间依然没有被唤醒,则会返回false |
long awaitNanos(long nanosTimeout) | 进入等待,并返回剩余等待的时间 | 剩余等待的时间,如果为正值则可以继续等待,为负值则说明等待已经超时 |
代码示例
有四名学生竞争同一个自习室的锁,他们分别是小王、小李、小美和小红。其中小王是一个大胃王,他在自习室学习一段时间后会感到饥饿,感到饥饿后他就会停止自习,当小王吃饱后才可以继续学习。而小李在学习时会发现工具缺失,无法继续学习,直到工具送到。
在Java并发编程学习(3):Monitor、wait-notify与保护性暂停中我们使用了wait-notify去解决这一问题。
在使用条件变量时,我们可以为工具和食物分别设置条件变量,并在达到要求时分别唤醒对应的线程。
public static final ReentrantLock room = new ReentrantLock();
public static final Condition waitFood = room.newCondition();
public static final Condition waitTool = room.newCondition();
public static boolean food = false;
public static boolean tool = false;
public static void main(String[] args) throws InterruptedException {
scheme4();
}
public static void scheme4() throws InterruptedException {
Thread wang = new Thread(() -> {
room.lock();
try {
log.info("学习一段时间");
while (!food){
try {
waitFood.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("吃饱了,继续学习");
}finally {
room.unlock();
}
}, "小王");
Thread li = new Thread(() -> {
room.lock();
try {
log.info("学习一段时间");
while (!tool){
try {
waitTool.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("工具到了,继续学习");
}finally {
room.unlock();
}
}, "小李");
Runnable task = () -> {
room.lock();
try {
log.info("学习");
}finally {
room.unlock();
}
};
Thread mei = new Thread(task, "小美");
Thread hong = new Thread(task, "小红");
wang.start();
TimeUnit.MILLISECONDS.sleep(100);
li.start();
mei.start();
hong.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
tool = true;
log.info("工具送到了");
room.lock();
try {
waitTool.signalAll();
}finally {
room.unlock();
}
}
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
food = true;
log.info("食物送到了");
room.lock();
try {
waitFood.signalAll();
}finally {
room.unlock();
}
}
}
使用ReentrantLock
解决哲学家就餐问题
哲学家就餐问题的死锁原因:每个人都只抢到一只筷子
思路:如果哲学家等待一段时间后能够放弃手中的筷子,让其他人先就餐就好了,而ReentrantLock
中存在trylock()
方法可以在得不到说的情况下执行其它指令,借助此方法,我们可以完成放弃的过程。
筷子类
为了让筷子具备锁的特性,需要让筷子类继承于ReentrantLock
。
class ChopstickWithReentrantLock extends ReentrantLock{
private String name;
public ChopstickWithReentrantLock(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
哲学家类
在哲学家类中,原来使用synchronized
的地方,现在需要使用ReentrantLock
进行改写。
class PhilosopherWithReentrantLock extends Thread{
private final ChopstickWithReentrantLock left;
private final ChopstickWithReentrantLock right;
public PhilosopherWithReentrantLock(String name, ChopstickWithReentrantLock left, ChopstickWithReentrantLock right) {
super(name);
this.left = left;
this.right = right;
}
@Override
while (true) {
if (left.tryLock()){
try {
if (right.tryLock()){
try {
eat();
}finally {
right.unlock();
}
}
}finally {
left.unlock();
}
}
}
}
private void eat(){
log.info("开始吃饭...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("饭吃完了");
}
}
测试类
public class PhilosopherEatingWithReentrantLock {
public static void main(String[] args) {
int N = 3;
ChopstickWithReentrantLock[] chopsticks = new ChopstickWithReentrantLock[N];
for (int i = 0; i < N; i++) {
chopsticks[i] = new ChopstickWithReentrantLock("筷子-"+i);
}
PhilosopherWithReentrantLock[] philosophers = new PhilosopherWithReentrantLock[N];
for (int i = 0; i < N; i++) {
philosophers[i] = new PhilosopherWithReentrantLock("哲学家-"+i,chopsticks[i],chopsticks[(i+1)%N]);
}
for (int i = 0; i < N; i++) {
philosophers[i].start();
}
}
}
在打印日志中,没有出现死锁现象。
设计模式:控制线程执行顺序
固定顺序
线程1打印数字t1,线程t2打印数字2,要求:先打印2,再打印1。
wait-notify实现
我们可以设置一个变量用来保存t2是否已经执行完成打印,如果没有,则线程t1进入等待状态,直到线程t2将它唤醒。
static final Object lock = new Object();
static boolean t2runned = false;
public static void main(String[] args) {
scheme1();
}
public static void scheme1(){
Runnable task1 = () -> {
synchronized (lock){
while (!t2runned){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("1");
}
};
Runnable task2 = () -> {
synchronized (lock){
log.info("2");
t2runned = true;
lock.notifyAll();
}
};
new Thread(task1,"t1").start();
new Thread(task2,"t2").start();
}
await-singal实现
static final ReentrantLock lock = new ReentrantLock();
static final Condition t2runCondtion = lock.newCondition();
static boolean t2runned = false;
public static void main(String[] args) {
scheme2();
}
public static void scheme2(){
Runnable task1 = () -> {
lock.lock();
try {
while (!t2runned){
try {
t2runCondtion.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("1");
}finally {
lock.unlock();
}
};
Runnable task2 = () -> {
lock.lock();
try {
log.info("2");
t2runned = true;
t2runCondtion.signalAll();
}finally {
lock.unlock();
}
};
new Thread(task1,"t1").start();
new Thread(task2,"t2").start();
}
park-unpark实现
此实现方法不需要使用锁对象,但是在执行unpark()
时需要获得其它线程对象。
public static void main(String[] args) {
scheme3();
}
public static void scheme3(){
Runnable task1 = () -> {
LockSupport.park();
log.info("1");
};
Thread t1 = new Thread(task1, "t1");
Runnable task2 = () -> {
log.info("2");
LockSupport.unpark(t1);
};
Thread t2 = new Thread(task2, "t2");
t1.start();
t2.start();
}
交替输出
有三个线程,分别输出5次a,5次b,5次c,要求最终的输出为abcabcabcabcabc的次序。
wait-notify实现
这里设置了一个等待标记flag
,并将打印方法抽象为void print(String str, int waitFlag, int nextFlag)
只有当flag
与waitFlag
相等时才会执行打印操作,并将flag
修改为nextFlag
同时唤醒其它线程。
根据题意,打印字符str
,waitFlag
与nextFlag
对应关系如下:
str | waitFlag | nextFlag |
---|---|---|
a | 1 | 2 |
b | 2 | 3 |
c | 3 | 1 |
static int flag = 1;
static int loopNum = 5;
public static void main(String[] args) {
scheme1();
}
static void scheme1(){
Runnable printa = () -> {
for (int i = 0; i < loopNum; i++) {
print("a",1,2);
}
};
Runnable printb = () -> {
for (int i = 0; i < loopNum; i++) {
print("b",2,3);
}
};
Runnable printc = () -> {
for (int i = 0; i < loopNum; i++) {
print("c",3,1);
}
};
new Thread(printa,"printer-a").start();
new Thread(printb,"printer-b").start();
new Thread(printc,"printer-c").start();
}
static synchronized void print(String str, int waitFlag, int nextFlag){
while (waitFlag != flag){
try {
CrossExecution.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(str);
flag = nextFlag;
CrossExecution.class.notifyAll();
}
await-singal实现
利用ReentrantLock
的特性,我们可以创建三个条件变量分别给三个线程使用,然后让他们相互唤醒。
具体思路为:
- 打印前,每个线程先执行
await()
方法等待; - 线程被唤醒后,打印内容,并使用
single()
唤醒下一个线程; - 主线程作为发起者,首先唤醒打印字符a的线程。
static int flag = 1;
static int loopNum = 5;
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
scheme2();
}
static void scheme2() throws InterruptedException {
Condition aCond = lock.newCondition();
Condition bCond = lock.newCondition();
Condition cCond = lock.newCondition();
Runnable printa = () -> {
for (int i = 0; i < loopNum; i++) {
print("a",aCond,bCond);
}
};
Runnable printb = () -> {
for (int i = 0; i < loopNum; i++) {
print("b",bCond,cCond);
}
};
Runnable printc = () -> {
for (int i = 0; i < loopNum; i++) {
print("c",cCond,aCond);
}
};
new Thread(printa,"printer-a").start();
new Thread(printb,"printer-b").start();
new Thread(printc,"printer-c").start();
// Thread.sleep(100);
lock.lock();
try {
aCond.signalAll();
}finally {
lock.unlock();
}
}
static void print(String str, Condition current, Condition next){
lock.lock();
try {
current.await();
System.out.print(str);
next.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
注意:以上的写法可能会使得主线程比printer-a先抢到锁,此时printer-a还没有执行到current.await();
而主线程却已经调用了aCond.signalAll();
。如果之后printer-a抢到锁并执行了current.await();
无人能够将它唤醒,这将导致程序卡死,没有输出。
解决方案:在主线程lock.lock();
前先睡眠一会。
park-unpark实现
在park-unpark实现中,考虑到线程t1的实现需要拿到线程t2,线程t2的实现需要拿到线程t3,线程t3的实现需要拿到线程t1,因此,需将它们设置为全局变量以避免产生循环定义。
public static void main(String[] args) throws InterruptedException {
scheme3();
}
static Thread t1;
static Thread t2;
static Thread t3;
static void scheme3(){
Runnable printa = () -> {
for (int i = 0; i < loopNum; i++) {
print("a",t2);
}
};
Runnable printb = () -> {
for (int i = 0; i < loopNum; i++) {
print("b",t3);
}
};
Runnable printc = () -> {
for (int i = 0; i < loopNum; i++) {
print("c",t1);
}
};
t1 = new Thread(printa);
t2 = new Thread(printb);
t3 = new Thread(printc);
t1.start();
t2.start();
t3.start();
LockSupport.unpark(t1);
}
static void print(String str, Thread next){
LockSupport.park();
System.out.print(str);
LockSupport.unpark(next);
}