1. 死锁的定义与影响
1.1 什么是死锁
- 发生在并发中,多个线程(进程)互不相让,相互持有对方所以需要的资源,又不主动释放,导致所有人都无法继续前进,导致程序陷入无尽的阻塞,就是死锁。
- 如上图两个人,红色球员说:你先给我球我就放手;蓝色球员说:你先放开我我就给你球
- 专业一点如下图:
-
多个线程(大于2)死锁如何发生
假设有三个线程:线程1持有锁A想要获取锁B;线程2持有锁B想要获取锁C;线程3持有锁C想要获取锁A;三者形成了一个环,谁也不先主动释放锁,谁也获取不到需要的锁,就形成了多个线程之间的死锁。
1.2 死锁的影响
- 死锁在不同系统中是不一样的,不一定死锁就一定有危害,取决于系统对死锁的处理能力。
- 数据库:某些数据库可以检测并放弃死锁。
- JVM:无法自动处理,但是可以检测。
- JVM死锁的发生几率不高但是危害很大,一旦发生,很多都是高并发场景,如果系统崩溃,非常影响用户。此外并发压力测试无法全部找出潜在的死锁。
2. 死锁的例子
2.1 死锁简单案例
- 代码案例就如上面两个线程相互等待的图一样,thread1获取了object1的锁,thread2获取了object2的锁,然后分别想要获取到对方的锁,由于两个线程互不相让,使得程序运行一直不会停止
/**
* @author yiren
*/
public class DeadLockMust {
private static Object object1 = new Object();
private static Object object2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (object1) {
System.out.println(Thread.currentThread().getName() + " object1");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object2) {
System.out.println(Thread.currentThread().getName() + " object2");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (object2) {
System.out.println(Thread.currentThread().getName() + " object2");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object1) {
System.out.println(Thread.currentThread().getName() + " object1");
}
}
});
thread1.start();
thread2.start();
}
}
Thread-1 object2
Thread-0 object1
- 如果IDEA强行点停止,退出编码会变为:130, 而正常的退出编码为0, 提示如下
Process finished with exit code 130 (interrupted by signal 2: SIGINT)
2.2 转账案例
- 两人互相转账,对转入转出账户加锁,形成相互依赖,然后发生死锁。
/**
* 两人互相转账
* @author yiren
*/
public class DeadlockTransferMoney {
public static void main(String[] args) throws InterruptedException {
Account a = new Account(1000);
Account b = new Account(1000);
Thread thread1 = new Thread(() -> {
try {
transferMoney(a, b,100);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread thread2 = new Thread(() -> {
try {
transferMoney(b, a, 200);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("a: " + a);
System.out.println("b: " + b);
}
public static void transferMoney(Account from, Account to, Integer amount) throws InterruptedException {
synchronized (from) {
TimeUnit.MILLISECONDS.sleep(500);
synchronized (to) {
if (from.balance - amount < 0) {
System.out.println("余额不足!转账失败!");
return;
}
from.balance -= amount;
to.balance += amount;
System.out.println("转账成功,转账:" + amount + "元");
}
}
}
}
- 这个程序和上面的线程死锁的案例代码原理是一样的。
2.3 多人随机转账案例
- 案例实现:随机向某个用户转账,并对转入转出账户先加锁。同时启N个线程
/**
* @author yiren
*/
public class DeadlockMultiTransferMoney {
private static final int ACCOUNTS_NUM = 50;
private static final int MONEY_NUM = 1000;
private static final int ITERATIONS_NUM = 1000000;
private static final int THREAD_NUM = 20;
private static Account[] accounts = new Account[ACCOUNTS_NUM];
static {
for (int i = 0; i < accounts.length; i++) {
accounts[i] = new Account(MONEY_NUM);
}
}
public static void main(String[] args) {
for (int i = 0; i < THREAD_NUM; i++) {
new TransferThread().start();
}
}
private static class TransferThread extends Thread {
@Override
public void run() {
for (int i = 0; i < ITERATIONS_NUM; i++) {
int fromAccount = new Random().nextInt(ACCOUNTS_NUM);
int toAccount = new Random().nextInt(ACCOUNTS_NUM);
int amount = new Random().nextInt(MONEY_NUM);
try {
transferMoney(accounts[fromAccount], accounts[toAccount], amount);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void transferMoney(Account from, Account to, Integer amount) throws InterruptedException {
synchronized (from) {
TimeUnit.MILLISECONDS.sleep(100);
synchronized (to) {
if (from.balance - amount < 0) {
System.out.println("余额不足!转账失败!");
return;
}
from.balance -= amount;
to.balance += amount;
System.out.println("转账成功,转账:" + amount + "元");
}
}
}
}
- 程序启动后,运行一段时间输出就会停止,虽然两个账户相互转账在众多账户中是小概率事件,但是还是会发生,互相转账形成死锁。
- 不仅是相互转账会发生死锁,还有如果N个人形成了一个环路,此时也会形成死锁。回顾一下上面说的3个线程的情况,把锁A B C换成三个账户即可。
假设有三个线程:线程1持有锁A想要获取锁B;线程2持有锁B想要获取锁C;线程3持有锁C想要获取锁A;三者形成了一个环,谁也不先主动释放锁,谁也获取不到需要的锁,就形成了多个线程之间的死锁。
3. 死锁的4个必要条件
-
互斥条件:一个资源每一次只能被一个线程使用。
-
请求与保持条件:一个线程持有第一把锁,又去请求第二把锁,然后第二把锁拿不到,就等待了,第一把锁也没释放掉。
-
不剥夺条件:多线程竞争资源,相互持有对方所以需要的资源时,线程不被外部因素干扰剥夺其竞争的权利。
-
循环等待条件:多线程竞争资源,相互持有对方所以需要的资源时,构成一个环路(循环依赖),不可解开。
4. 定位死锁
4.1 jstack定位死锁详解
- 以上面的DeadlockMust为例
- 我们先找出pid --> 使用
jps
命令
> jps
52049 Launcher
52050 DeadlockMust
52106 Jps
46876
...
- 然后使用jstack pid
> jstack 52050
2020-02-14 19:15:57
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.241-b07 mixed mode):
"Attach Listener" #14 daemon prio=9 os_prio=31 tid=0x00007fdfbe840000 nid=0xa403 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"DestroyJavaVM" #13 prio=5 os_prio=31 tid=0x00007fdfc1099800 nid=0x1003 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
.......
.......
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x00007fdfbd011f78 (object 0x000000076ac2a7e8, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x00007fdfbd014758 (object 0x000000076ac2a7f8, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at com.imyiren.concurrency.deadlock.DeadlockMust.lambda$main$1(DeadlockMust.java:35)
- waiting to lock <0x000000076ac2a7e8> (a java.lang.Object)
- locked <0x000000076ac2a7f8> (a java.lang.Object)
at com.imyiren.concurrency.deadlock.DeadlockMust?Lambda$2/2129789493.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at com.imyiren.concurrency.deadlock.DeadlockMust.lambda$main$0(DeadlockMust.java:22)
- waiting to lock <0x000000076ac2a7f8> (a java.lang.Object)
- locked <0x000000076ac2a7e8> (a java.lang.Object)
at com.imyiren.concurrency.deadlock.DeadlockMust?Lambda$1/1607521710.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
- 中间的部分信息省去。
- 我们先看
Found one Java-level deallock:
告诉我们发现一个死锁。然后下面告诉我们Thread-1和Thread-0分别等待那个锁,这个锁被谁持有。如下:Thread-1
在等待0x00007fdfbd011f78
被Thread-0持有
"Thread-1":
waiting to lock monitor 0x00007fdfbd011f78 (object 0x000000076ac2a7e8, a java.lang.Object),
which is held by "Thread-0"
- 然后我们找到
Java stack information for the threads listed above:
下面的信息 - 看
Thread-1
:- waiting to lock <0x000000076ac2a7e8>
等待****7e8的锁
;locked <0x000000076ac2a7f8>
持有****7f8
"Thread-1":
at com.imyiren.concurrency.deadlock.DeadlockMust.lambda$main$1(DeadlockMust.java:35)
- waiting to lock <0x000000076ac2a7e8> (a java.lang.Object)
- locked <0x000000076ac2a7f8> (a java.lang.Object)
at com.imyiren.concurrency.deadlock.DeadlockMust?Lambda$2/2129789493.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
- 看
Thread-10:
- waiting to lock <0x000000076ac2a7f8>等待
****7f8的锁;
locked <0x000000076ac2a7e8>持有
****7e8`
"Thread-0":
at com.imyiren.concurrency.deadlock.DeadlockMust.lambda$main$0(DeadlockMust.java:22)
- waiting to lock <0x000000076ac2a7f8> (a java.lang.Object)
- locked <0x000000076ac2a7e8> (a java.lang.Object)
at com.imyiren.concurrency.deadlock.DeadlockMust?Lambda$1/1607521710.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
- 我们按照思路再看下上面
DeadlockMultiTransferMoney
类的死锁,直接看jstack
信息
Found one Java-level deadlock:
=============================
"Thread-19":
waiting to lock monitor 0x00007fc7f401a738 (object 0x000000076ac2f280, a com.imyiren.concurrency.deadlock.Account),
which is held by "Thread-1"
"Thread-1":
waiting to lock monitor 0x00007fc7f700d4b8 (object 0x000000076ac2f1e0, a com.imyiren.concurrency.deadlock.Account),
which is held by "Thread-15"
"Thread-15":
waiting to lock monitor 0x00007fc7f700e7f8 (object 0x000000076ac2f3a0, a com.imyiren.concurrency.deadlock.Account),
which is held by "Thread-8"
"Thread-8":
waiting to lock monitor 0x00007fc7f700d408 (object 0x000000076ac2f460, a com.imyiren.concurrency.deadlock.Account),
which is held by "Thread-5"
"Thread-5":
waiting to lock monitor 0x00007fc7f8016ff8 (object 0x000000076ac2f270, a com.imyiren.concurrency.deadlock.Account),
which is held by "Thread-18"
"Thread-18":
waiting to lock monitor 0x00007fc7f700d408 (object 0x000000076ac2f460, a com.imyiren.concurrency.deadlock.Account),
which is held by "Thread-5"
Java stack information for the threads listed above:
===================================================
"Thread-19":
at com.imyiren.concurrency.deadlock.DeadlockMultiTransferMoney.transferMoney(DeadlockMultiTransferMoney.java:49)
- waiting to lock <0x000000076ac2f280> (a com.imyiren.concurrency.deadlock.Account)
at com.imyiren.concurrency.deadlock.DeadlockMultiTransferMoney$TransferThread.run(DeadlockMultiTransferMoney.java:38)
"Thread-1":
at com.imyiren.concurrency.deadlock.DeadlockMultiTransferMoney.transferMoney(DeadlockMultiTransferMoney.java:51)
- waiting to lock <0x000000076ac2f1e0> (a com.imyiren.concurrency.deadlock.Account)
- locked <0x000000076ac2f280> (a com.imyiren.concurrency.deadlock.Account)
at com.imyiren.concurrency.deadlock.DeadlockMultiTransferMoney$TransferThread.run(DeadlockMultiTransferMoney.java:38)
....
....
Found 1 deadlock.
- 注意:
jstack
是不一定能准确得告诉我们发生了死锁的,如果遇到jstack
分析不出来的,我们可以通过信息自行分析。
4.2 代码定位死锁 --- ThreadMXBean
/**
* 使用ThreadMXBean检测死锁
* 使用DeadlockMust改造
* @author yiren
*/
public class ThreadMxBeanDetection {
private static Object object1 = new Object();
private static Object object2 = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
synchronized (object1) {
System.out.println(Thread.currentThread().getName() + " object1");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object2) {
System.out.println(Thread.currentThread().getName() + " object2");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (object2) {
System.out.println(Thread.currentThread().getName() + " object2");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object1) {
System.out.println(Thread.currentThread().getName() + " object1");
}
}
});
thread1.start();
thread2.start();
// 等待死锁发生
TimeUnit.SECONDS.sleep(2);
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (null != deadlockedThreads && deadlockedThreads.length > 0) {
for (int i = 0; i < deadlockedThreads.length; i++) {
ThreadInfo threadInfo = threadMXBean.getThreadInfo(deadlockedThreads[i]);
System.out.println("found deadlock thread: " + threadInfo.getThreadName());
}
}
}
}
Thread-0 object1
Thread-1 object2
found deadlock thread: Thread-1
found deadlock thread: Thread-0
5. 修复死锁的策略
5.1 生产环境发生死锁怎么办?
-
一般线上发生死锁,不可提前预料,且蔓延非常快,影响特别大,所以我们需要防范于未然。
-
如果真的发生了,首先应该保存案发现场,然后迅速恢复服务。恢复后再排查修复问题。
5.2 修复策略一:避免策略
- 哲学家就餐的换手方案,转账更换顺序方案
- 哲学家就餐问题:wiki:哲学家就餐问题点击查看
- 死锁的发生:每个哲学家都拿起左边的餐具,再去拿右边的餐具,就陷入了等待。
/**
* 哲学家问题
*
* @author yiren
*/
public class DiningPhilosophers {
public static void main(String[] args) {
Philosopher[] philosophers = new Philosopher[5];
Object[] objects = new Object[philosophers.length];
for (int i = 0; i < objects.length; i++) {
objects[i] = new Object();
}
for (int i = 0; i < philosophers.length; i++) {
philosophers[i] = new Philosopher(objects[i], objects[(i+1)%objects.length]);
new Thread(philosophers[i], "philosopher-" + (i + 1)).start();
}
}
private static class Philosopher implements Runnable {
private Object left;
private Object right;
public Philosopher(Object left, Object right) {
this.left = left;
this.right = right;
}
@Override
public void run() {
try {
while (true) {
// thinking...
action("thinking...");
synchronized (left) {
// Pick up left.
action("Pick up left ");
synchronized (right) {
// Pick up right and eating
action("Pick up right and eating ");
action("Put down right");
}
action("Put down left");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void action(String action) throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " do " + action);
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(10));
}
}
}
-
避免策略解决:
- 找服务员做协调,来分发餐具
- 改变一个哲学家拿餐具的顺序
- 给一个餐具减一数量的信号量相当于餐票
-
检测与恢复策略:
- 第三方敢于,剥夺某个哲学家的吃饭条件
-
改变一个哲学家拿餐具的顺序
我们可以替换创建哲学家的时候的一个人左右顺序,修改上mian函数代码如下:
public static void main(String[] args) { Philosopher[] philosophers = new Philosopher[5]; Object[] objects = new Object[philosophers.length]; for (int i = 0; i < objects.length; i++) { objects[i] = new Object(); } for (int i = 0; i < philosophers.length; i++) { if (philosophers.length - 1 == i) { philosophers[i] = new Philosopher(objects[(i + 1) % objects.length], objects[i]); } else { philosophers[i] = new Philosopher(objects[i], objects[(i + 1) % objects.length]); } new Thread(philosophers[i], "philosopher-" + (i + 1)).start(); } }
- 相互转账问题:我们可以把上面两个转账时获取锁的顺序给修正一下,让相互转账获取锁的顺序一致。代码如下:
/**
* 两人互相转账 修复死锁
*
* @author yiren
*/
public class DeadlockTransferMoneyFix {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Account a = new Account(1000);
Account b = new Account(1000);
Thread thread1 = new Thread(() -> {
try {
transferMoney(a, b, 100);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread thread2 = new Thread(() -> {
try {
transferMoney(b, a, 200);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("a: " + a);
System.out.println("b: " + b);
}
public static void transferMoney(Account from, Account to, Integer amount) throws InterruptedException {
int fromHashCode = from.hashCode();
int toHashCode = to.hashCode();
if (fromHashCode > toHashCode) {
synchronized (from) {
synchronized (to) {
transfer(from, to, amount);
}
}
return;
} else if (fromHashCode < toHashCode) {
synchronized (to) {
synchronized (from) {
transfer(from, to, amount);
}
}
} else {
synchronized (lock) {
transfer(from, to, amount);
}
}
}
private static void transfer(Account from, Account to, Integer amount) {
if (from.balance - amount < 0) {
System.out.println("余额不足!转账失败!");
return;
}
from.balance -= amount;
to.balance += amount;
System.out.println("转账成功,转账:" + amount + "元");
}
}
转账成功,转账:100元
转账成功,转账:200元
a: Account{balance=1100}
b: Account{balance=900}
Process finished with exit code 0
- 代码中使用
hashCode
来排序,因为hashCode有重复,所以我们需要处理等于的情况,但在业务中我们也可以使用账户唯一ID之类的来做一个排序
5.3 修复策略二:检测恢复策略
-
定时检测是否有死锁,如果有就剥夺某个资源以打开死锁
-
检测算法:锁的调用链路图
-
允许发生死锁
-
每次调用所都记录
-
定期检查“锁的调用链路图”中是否存在环路
-
如果存在就意味着发生了死锁
-
然后我们就用指定的恢复策略去解决死锁
-
-
恢复策略:
- 进程终止:
- 逐个终止线程,直到死锁消除
- 终止顺序:考虑(1)优先级 (2)占用资源比 (3)运行时间
- 资源抢占:
- 分发出去的锁给收回来
- 让线程回退几步,不用整体结束线程,成本低
- 缺点:可能造成某些线程一直得不到运行,产生饥饿
- 进程终止:
5.4 修复策略三:鸵鸟策略
- 鸵鸟策略就是之当鸵鸟遇到危险和困难时,总是把自己的头埋在沙子里,一动也不动,就是掩耳盗铃的意思。等同于我们如果知道发生死锁的概率极低,我们可以直接忽略它,等到它发生了,再人工修复。
6. 避免死锁的发生
6.1 方式一:设置超时时间
-
Lock的tryLock(long timeout, TimeUnit unit)
-
synchronized不具备这样的尝试获取锁能力
-
造成超时的可能性很多如:发生死锁、死循环、代码执行慢
-
无论如何,我们只要超时时间到了,就认为是获取锁失败,然后进行失败处理,打日志、报警、重启等等
/**
* 使用tryLock来避免死锁
*
* @author yiren
*/
public class DeadlockTryLock {
private static Lock lock1 = new ReentrantLock();
private static Lock lock2 = new ReentrantLock();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
try {
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + " got lock 1");
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(10));
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + " got lock1 and lock2 successfully.");
lock2.unlock();
lock1.unlock();
break;
} else {
System.out.println(Thread.currentThread().getName() + " fail to get lock2");
lock1.unlock();
}
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(10));
} else {
System.out.println(Thread.currentThread().getName() + " fail to get lock1");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
try {
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + " got lock 2");
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(10));
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + " got lock2 and lock1 successfully.");
lock1.unlock();
lock2.unlock();
break;
} else {
System.out.println(Thread.currentThread().getName() + " fail to get lock1");
lock2.unlock();
}
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(10));
} else {
System.out.println(Thread.currentThread().getName() + " fail to get lock2");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
}
}
Thread-0 got lock 1
Thread-1 got lock 2
Thread-1 fail to get lock1
Thread-0 got lock1 and lock2 successfully.
Thread-1 got lock 2
Thread-1 got lock2 and lock1 successfully.
Process finished with exit code 0
6.2 使用并发类
-
`ConcurrentHashMap、ConcurrentLinkedQueue等
-
java.util.concurrent.aotmic.*
下的原子类也是简单好用效率也高 -
优先使用并发集合而不是同步集合
6.3 其他的一些点
-
我们在加锁的时候,尽量降低锁的颗粒度,用不同的锁做不同的事情,不用一个锁
-
能用同步代码块就不用同步方法,并且自己指定锁对象,方便自己控制
-
尽量给线程命名,使其有意义,方便排查问题。
-
我们应该尽量避免锁的嵌套,锁的嵌套容易引起死锁
-
分配资源前,考虑能不能收回来:银行家算法
-
尽量不要多个功能使用同一把锁:专锁专用
7. 其他活性故障
- 死锁是最常见的活跃性问题,除了死锁外,还有一些类似的问题也会导致程序无法成功执行,统称为活跃性问题。也就是活锁、饥饿
7.1 活锁(LiveLock)
-
什么是活锁?
活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。处于活锁的实体是在不断的改变状态,活锁有可能自行解开。
- 就比如:哲学家问题,先同时拿起左边的餐具,然后等待5分钟,又同时放下,再等待五分钟,又同时去尝试拿去左边的餐具.....; 我们可以把等待的事件设置成随机数,就可以解决
虽然线程没有阻塞,始终在运行,但是程序得不到进展,线程始终在重复同样的事情
-
代码演示:
/**
* 活锁问题
*
* @author yiren
*/
public class LiveLock {
private static class Spoon {
private Diner owner;
public Spoon(Diner owner) {
this.owner = owner;
}
public synchronized void use() {
System.out.println(owner.name + " has eaten!");
}
}
private static class Diner {
private String name;
private boolean isHungry;
public Diner(String name) {
this.name = name;
this.isHungry = true;
}
public void eatWith(Spoon spoon, Diner spouse) {
while (isHungry) {
if (spoon.owner != this) {
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
continue;
}
if (spouse.isHungry) {
System.out.println(name + " : " + spouse.name + " first!");
spoon.owner = spouse;
continue;
}
spoon.use();
isHungry = false;
System.out.println(name + " : finished!");
spoon.owner = spouse;
}
}
}
public static void main(String[] args) {
Diner man = new Diner("Man");
Diner woman = new Diner("Women");
Spoon spoon = new Spoon(man);
Thread manThread = new Thread(() -> {
man.eatWith(spoon,woman);
});
Thread womanThread = new Thread(() -> {
woman.eatWith(spoon,man);
});
manThread.start();
womanThread.start();
}
}
Women : Man first!
Man : Women first!
Women : Man first!
Man : Women first!
Women : Man first!
Man : Women first!
Women : Man first!
Man : Women first!
Women : Man first!
Man : Women first!
Women : Man first!
Man : Women first!
Women : Man first!
Man : Women first!
Women : Man first!
Man : Women first!
.......
-
互相谦让,始终循环在此逻辑里面。怎么解决呢?
加入随机因素,让重试策略更加合理,修改上面的代码中的
eatWith
方法public void eatWith(Spoon spoon, Diner spouse) { while (isHungry) { if (spoon.owner != this) { try { TimeUnit.MILLISECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } continue; } Random random = new Random(); if (spouse.isHungry && random.nextInt(10) < 7) { System.out.println(name + " : " + spouse.name + " first!"); spoon.owner = spouse; continue; } spoon.use(); isHungry = false; System.out.println(name + " : finished!"); spoon.owner = spouse; } } }
Man : Women first! Women : Man first! Man has eaten! Man : finished! Women has eaten! Women : finished! Process finished with exit code 0
除此之外,我们还可以增加重试机制。如果失败多次过后,使用补偿策略处理。
7.2 饥饿
- 当线程需要某些资源,但始终得不到(如CPU资源),饥饿会导致响应性能变差,需要等待很久甚至超时失败。
- 例如:线程的优先级过低、线程持有锁同时又无限循环不释放锁、程序始终占用某资源的写锁等
- 程序设计不应该依赖于优先级
- 线程优先级在不同操作系统不一样,在Java中设置的优先级可能在不同平台上对应的不一样
- 此外,操作系统可能会改变线程的优先级,使得其优先级变低等
8. 面试常考问题
- 写一个必然死锁的例子
- 发生死锁必须满足哪些条件
- 如何定位死锁
- 有哪些解决死锁问题的策略?
- 讲讲经典的哲学家就餐问题
- 实际工程中如何避免死锁
- 什么是活跃性问题?活锁、饥饿和死锁有什么区别?