目录
1.什么是死锁
2.发生死锁的例子
2.1简单的例子
2.2生产中的例子-转账
2.3模拟多人转账
3.死锁的4个必要条件
4.如何定位死锁
5.修复死锁的策略
5.1线上发生死锁怎么办?
5.2常见修复的策略
6.实际工作中如何避免死锁
1.什么是死锁
- 发生在并发中
- 互不相让:当两个(或更多)线程(或进程)相互持有对方所需要的资源,又不主动释放,导致所有人都无法继续前进,导致程序陷入无尽的阻塞,这就是死锁
2.发生死锁的例子
2.1简单的例子
- 当类的对象flag=1时(T1),先锁定O1,睡眠500毫秒,然后锁定O2
- 而T1在睡眠的时候另一个flag=0的对象(T2)启动,先锁定O2,睡眠500毫秒,等待T1释放O1
- T1睡眠结束后需要锁定O2才能继续执行,而此时O2已被T2锁定
- T2睡眠结束后需要锁定 O1才能继续执行,而此时O1已被T1锁定
- T1和T2互相等待,都需要对方多订的资源才能继续执行,从而产生死锁
public class MustDeadLock implements Runnable {
int flag = 1;
static Object o1 = new Object();
static Object o2 = new Object();
public static void main(String[] args) {
MustDeadLock r1 = new MustDeadLock();
MustDeadLock r2 = new MustDeadLock();
r1.flag = 1;
r2.flag = 0;
new Thread(r1).start();
new Thread(r2).start();
}
@Override
public void run() {
if (flag == 1){
synchronized (o1){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
System.out.println("r1成功拿到两把锁");
}
}
}
if (flag == 0){
synchronized (o2){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
System.out.println("r2成功拿到两把锁");
}
}
}
}
}
2.2生产中的例子-转账
- 需要两把锁
- 获取两把锁成功,且余额大于0,则扣除转出人,增加收款人的余额,是原子操作
- 顺序相反导致死锁
public class TransferMoney implements Runnable{
int flag = 1;
static Account a = new Account(500);
static Account b = new Account(500);
public static void main(String[] args) throws InterruptedException {
TransferMoney r1 = new TransferMoney();
TransferMoney r2 = new TransferMoney();
r1.flag = 1;
r2.flag = 0;
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
t1.join();
t1.join();
System.out.println("a的余额"+a.balance);
System.out.println("b的余额"+b.balance);
}
@Override
public void run() {
if (flag ==1){
transferMoney(a,b,200);
}
if (flag ==0){
transferMoney(b,a,200);
}
}
public static void transferMoney(Account from, Account to, int amount) {
synchronized (from){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (to){
if (from.balance - amount <0){
System.out.println("余额不足,转账失败");
}
from.balance-=amount;
to.balance+=amount;
System.out.println("转账成功");
}
}
}
static class Account{
int balance;
public Account(int balance) {
this.balance = balance;
}
}
}
2.3模拟多人转账
public class MultTransferMoney {
public static final int NUM_ACCOUNTS = 500;
public static final int NUM_MONEY = 1000;
public static final int NUM_ITERATIONS = 1000;
public static final int NUM_THREADS = 20;
public static void main(String[] args) {
final Random rnd = new Random();
final Account[] accounts = new Account[NUM_ACCOUNTS];
for (int i = 0; i < accounts.length; i++) {
accounts[i] = new Account(NUM_MONEY);
}
class TransferThread extends Thread{
@Override
public void run(){
for (int i = 0; i < NUM_ITERATIONS; i++) {
int fromAcct = rnd.nextInt(NUM_ACCOUNTS);
int toAcct = rnd.nextInt(NUM_ACCOUNTS);
int amount = rnd.nextInt(NUM_MONEY);
TransferMoney.transferMoney(accounts[fromAcct],accounts[toAcct],amount);
}
}
}
for (int i = 0; i < NUM_THREADS; i++) {
new TransferThread().start();
}
}
}
3.死锁的4个必要条件
4.如何定位死锁
使用ThreadMaxBean
public class ThreadMXBeanD implements Runnable{
int flag = 1;
static Object o1 = new Object();
static Object o2 = new Object();
public static void main(String[] args) {
ThreadMXBeanD r1 = new ThreadMXBeanD();
ThreadMXBeanD r2 = new ThreadMXBeanD();
r1.flag = 1;
r2.flag = 0;
new Thread(r1).start();
new Thread(r2).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
final long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (deadlockedThreads != null && deadlockedThreads.length > 0){
for (int i = 0; i < deadlockedThreads.length; i++) {
final ThreadInfo threadInfo = threadMXBean.getThreadInfo(deadlockedThreads[i]);
// 发生死锁Thread-1
// 发生死锁Thread-0
System.out.println("发生死锁"+threadInfo.getThreadName());
}
}
}
@Override
public void run() {
if (flag == 1){
synchronized (o1){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
System.out.println("r1成功拿到两把锁");
}
}
}
if (flag == 0){
synchronized (o2){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
System.out.println("r2成功拿到两把锁");
}
}
}
}
}
5.修复死锁的策略
5.1线上发生死锁怎么办?
- 线上问题都需要防患于未然,不造成损失扑灭几乎已经是不可能
- 保存案发现场然后重启服务器
- 暂时保证线上服务的安全,然后在利用刚才保存的信息,排查死锁,修改代码,重新发版
5.2常见修复的策略
- 避免策略:哲学家就餐的换手方案,转账换序方案
- 检测与恢复策略:一段时间检测是否有死锁,如果有就剥夺某一个资源,就打开死锁
- 鸵鸟策略:如果我们发生死锁的概率非常低,那么我就直接忽略它,知道死锁的发生的时候,再人生修复
6.实际工作中如何避免死锁
- 设置超时时间
- 多食用并发类而不是自己设计类
- 尽量降低所得使用粒度,用不同的锁而不是同一个锁
- 如果能使用同步代码块,就不使用同步方法,自己指定锁对象
- 给你的线程起个有意义的名字,debug和排查的时事半功倍,框架和JDK都遵守这个最佳实践
- 避免锁的嵌套:MustDeadLock
- 分配资源前先看能不能收回来
- 尽量不要几个功能用同一把锁:专锁专用
|