1 什么是死锁?
1、发生在并发中(单线程不会死锁)
2、互不相让(>=2个线程互相持有对方所需要的资源)
两个线程造成死锁的情况:
多个线程造成死锁的情况:
多个线程的依赖关系是环形的
2 死锁的影响
死锁的影响在不同的系统中不一样
取决于系统对死锁的处理能力
数据库:检测并放弃事务
JVM:无法自动处理(死锁的处理有很多种,JVM不能做到十全十美)【有工具可以检测】
几率不高但危害大
1)一旦发生,多是高并发场景,影响了多个用户
2)整个系统崩溃、子系统崩溃、性能降低
注意:压力测试无法找出所有潜在的死锁(死锁与并发量有关系,但是不是确定性的关系,而死锁是随机触发的)
3发生死锁的例子
3.1最简单的情况
/**
* @description 必定发生死锁的情况
*/
public class MustDeadLock implements Runnable {
int flag = 1;
static Object o1 = new Object();
static Object o2 = new Object();
public static void main(String[] args) throws InterruptedException {
MustDeadLock r1 = new MustDeadLock();
MustDeadLock r2 = new MustDeadLock();
r1.flag = 1;
r2.flag = 0;
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
@Override
public void run() {
System.out.println("flag = " + flag);
if (flag == 1) {
synchronized (o1) {
try {
System.out.println(Thread.currentThread().getName() + " 获得o1");
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + " 等待o2");
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 获得o1和o2");
Thread.sleep(500);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
if (flag == 0) {
synchronized (o2) {
try {
System.out.println(Thread.currentThread().getName() + " 获得o2");
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + " 等待o1");
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + " 获得o1和o2");
Thread.sleep(500);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
图示:
运行结果:
注意看退出信号:Exit code 130(不正常退出)
3.2实际生产中的例子
转账
1)需要两把锁(一把锁自己,一把锁对方)
2)获得两把锁成功,余额>0,扣除转出人,增加收款人的余额,是原子操作
3)顺序相反导致死锁
3.3模拟多人随机转账
5万人,依然会发生死锁
import java.util.Random;
/**
* 多人同时转账 依然很危险
*/
public class MultiTransferMoney {
private static final int NUM_ACCOUNTS = 50;
private static final int NUM_MONEY = 1000;
private static final int NUM_ITERATIONS = 1000000;
private static final int NUM_THREAD = 20;
public static void main(String[] args) {
Random random = new Random();
TransferMoney.Account[] accounts = new TransferMoney.Account[NUM_ACCOUNTS];
for (int i = 0; i < accounts.length; i++) {
accounts[i] = new TransferMoney.Account(i, NUM_MONEY);
}
class TransferThread extends Thread {
@Override
public void run() {
for (int i = 0; i < NUM_ITERATIONS; i++) {
int fromAcct = random.nextInt(NUM_ACCOUNTS);
int toAcct = random.nextInt(NUM_ACCOUNTS);
int amount = random.nextInt(NUM_MONEY);
if (fromAcct != toAcct) {
TransferMoney.transferMoney(accounts[fromAcct], accounts[toAcct], amount);
}
}
System.out.println("==========运行结束==========");
}
}
// 创建20个线程
for (int i = 0; i < NUM_THREAD; i++) {
new TransferThread().start();
}
}
}
4 死锁的4个必要条件
(1)互斥
(2)请求与保持(不释放)
(3)不剥夺(没有裁判能够剥夺某个线程对资源的保持)
(4)循环等待(循环依赖)
1)jstack分析死锁
1、找到java程序的pid(jps)
2、${JAVA_HOME}/bin/jstack -l pid
分析“多人转账”
2) ThreadMXBean代码演示(裁判,定位并修复死锁)
(1)ManagementFactory.getThreadMXBean()
(2)打印所有死锁的线程信息
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
/**
* 用ThreadMXBean检测死锁
*/
public class ThreadMXBeanDetection implements Runnable {
int flag = 1;
static Object o1 = new Object();
static Object o2 = new Object();
public static void main(String[] args) throws InterruptedException {
ThreadMXBeanDetection r1 = new ThreadMXBeanDetection();
ThreadMXBeanDetection r2 = new ThreadMXBeanDetection();
r1.flag = 1;
r2.flag = 0;
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
Thread.sleep(1000);
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (deadlockedThreads != null && deadlockedThreads.length > 0) {
for (int i = 0; i < deadlockedThreads.length; i++) {
ThreadInfo threadInfo = threadMXBean.getThreadInfo(deadlockedThreads[i]);
System.out.println("发现死锁" + threadInfo.getThreadName());
}
}
}
@Override
public void run() {
System.out.println("flag = " + flag);
if (flag == 1) {
synchronized (o1) {
try {
System.out.println(Thread.currentThread().getName() + " 获得o1");
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + " 等待o2");
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 获得o1和o2");
Thread.sleep(500);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
if (flag == 0) {
synchronized (o2) {
try {
System.out.println(Thread.currentThread().getName() + " 获得o2");
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + " 等待o1");
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + " 获得o1和o2");
Thread.sleep(500);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
5.修复死锁的策略
1)线上
1、防患于未然(不可提前预料、蔓延速度快、危害大)
2、保持案发现场,立刻重启服务器
3、保证线上服务的安全,根据保存的案发现场,排查死锁,修改代码
2)线下
1、避免策略(哲学家就餐-换手、转账换序)
思路:避免相反的获取锁的顺序
(锁获取的顺序不影响该策略的执行)
如果hashCode相同,则需要加时赛
如果数据库,则主键用来避免死锁更好
2、检测与恢复策略(有死锁,则剥夺)
3、鸵鸟策略(忽略概率低的死锁,出现死锁再人工修复)