目录
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个必要条件
- 互斥,共享资源X和Y只能被一个线程占用;
- 占有且等待,线程T1已经取得共享资源X,在等待共享资源Y的时候,不释放共享资源X;
- 不可抢占,其他线程不能强行抢占线程T1占有的资源;
- 循环等待,线程T1等待线程T2占有的资源,线程T2等待线程T1占有的资源,就是循环等待。
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破坏占用且等待条件,一次性申请全部资源
class Allocator {
private List<Object> als = new ArrayList<>();
// 一次性申请所有资源
synchronized boolean apply(
Object from, Object to){
if(als.contains(from) ||
als.contains(to)){
return false;
} else {
als.add(from);
als.add(to);
}
return true;
}
// 归还资源
synchronized void free(
Object from, Object to){
als.remove(from);
als.remove(to);
}
}
class Account {
// actr应该为单例
private Allocator actr;
private int balance;
// 转账
void transfer(Account target, int amt){
// 一次性申请转出账户和转入账户,直到成功
while(!actr.apply(this, target))
;
try{
// 锁定转出账户
synchronized(this){
// 锁定转入账户
synchronized(target){
if (this.balance > amt){
this.balance -= amt;
target.balance += amt;
}
}
}
} finally {
actr.free(this, target)
}
}
}
5.2破坏循环等待条件
class Account {
private int id;
private int balance;
// 转账
void transfer(Account target, int amt){
Account left = this
Account right = target;
if (this.id > target.id) {
left = target;
right = this;
}
// 锁定序号小的账户
synchronized(left){
// 锁定序号大的账户
synchronized(right){
if (this.balance > amt){
this.balance -= amt;
target.balance += amt;
}
}
}
}
}
6.实际工作中如何避免死锁
- 设置超时时间
- 多食用并发类而不是自己设计类
- 尽量降低所得使用粒度,用不同的锁而不是同一个锁
- 如果能使用同步代码块,就不使用同步方法,自己指定锁对象
- 给你的线程起个有意义的名字,debug和排查的时事半功倍,框架和JDK都遵守这个最佳实践
- 避免锁的嵌套:MustDeadLock
- 分配资源前先看能不能收回来
- 尽量不要几个功能用同一把锁:专锁专用