一、死锁是个啥?
关于银行转账问题,a转给b,c转给d,涉及到两个用户,那么是否可以拿两把锁来操作呢?如下:
package exercise_01_0705_synchronized;
public class Account2 {
private Integer balance = 10000; // 账户余额
// 转账操作
void transfer(Account2 account2,int amt) throws InterruptedException {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " this lock");
Thread.sleep(1000);
synchronized (account2) {
System.out.println(Thread.currentThread().getName() + " account2 lock");
account2.balance += amt;
Thread.sleep(1000);
this.balance -= amt;
System.out.println(Thread.currentThread().getName() + " account2 unlock");
}
System.out.println(Thread.currentThread().getName() + " this unlock");
}
}
public static void main(String[] args) {
Account2 account = new Account2();
Account2 account2 = new Account2();
Account2 account3 = new Account2();
Thread a = new Thread(()->{
try {
account.transfer(account2,100);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread b = new Thread(()->{
try {
account2.transfer(account,90);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
a.start();
b.start();
}
}
这种骚操作就可能引起死锁哦。 如下:
转账发生死锁时候的资源分配图
二、死锁产生的四个必要条件
1、互斥,资源x和资源y都只能被一个线程占用;(锁的基本条件咯)
2、占有且等待,线程t1已经获得共享资源x,再去获得资源y的时候,不释放共享资源x;
3、不可抢占,其他线程不可抢占线程t1的资源;
4、循环等待,线程t1等线程t2的资源,线程t2等待线程t1的资源。
三、如何避免死锁?
破坏如上其中任一一个条件即可。
3.1 互斥(不可行)
这个不可以被破坏,锁的基本条件
3.2 破坏占有且等待条件
可以一次性申请所有所需资源。
如之前的转账问题,可以建立一个管理员来处理,逻辑如下:
代码:
单例模式的管理员:
package exercise_01_0705_synchronized;
import java.util.ArrayList;
import java.util.List;
// 单例模式
public class Allocator {
private volatile static Allocator allocator = null; //volatitle 解决指令重排序的问题
private static List<Object> lockList = new ArrayList<>();
private Allocator() {
} // 构造方法私有
public static Allocator getAllocator() { //synchronized 保证线程安全
if (allocator == null) {
// 保证所有线程都可以进入,不会因为锁方法而把类锁上,也可以过滤很多,提高性能
synchronized (Allocator.class) { //开始锁住
if (allocator == null) { //防止两个线程同城到达if,又出现线程不安全的问题,双重检查锁
allocator = new Allocator();
}
}
}
return allocator;
}
// 拿到资源
public synchronized boolean getLock(Object a, Object b) {
if (lockList.contains(a) || lockList.contains(b)) {
System.out.println("false");
return false;
}
lockList.add(a);
lockList.add(b);
System.out.println("true");
return true;
}
// 释放资源
public synchronized void removeLock(Object a, Object b) {
lockList.remove(a);
lockList.remove(b);
}
}
新的转账接口和测试代码:
package exercise_01_0705_synchronized;
public class AccountNew {
private Integer balance = 10000; // 账户余额
// 转账操作
void transfer(AccountNew account2, int amt) throws InterruptedException {
while (!Allocator.getAllocator().getLock(this, account2)) {
Thread.sleep(1000);
}
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " this lock");
synchronized (account2) {
System.out.println(Thread.currentThread().getName() + " account2 lock");
account2.balance += amt;
this.balance -= amt;
System.out.println(Thread.currentThread().getName() + " account2 unlock");
}
System.out.println(Thread.currentThread().getName() + " this unlock");
}
Allocator.getAllocator().removeLock(this, account2);
}
public static void main(String[] args) {
AccountNew account = new AccountNew();
AccountNew account2 = new AccountNew();
Thread a = new Thread(()->{
try {
account.transfer(account2,100);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread b = new Thread(()->{
try {
account2.transfer(account,90);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
a.start();
b.start();
}
}
运行结果:解决了死锁问题哦
3.3 破坏不可抢占条件(synchronized 做不到)
synchronized 申请资源的时候,如果没有申请到,则会线程阻塞,并不会释放已有资源。
其他lock是可以做到的(了解后回来补充,嘻嘻)。
3.4 破坏循环等待条件
对资源进行排序,然后按顺序申请资源即可。
代码实现:
package exercise_01_0705_synchronized;
public class AccountSequence {
private Integer id;
private Integer balance = 10000; // 账户余额
// 转账操作
void transfer(AccountSequence accountSequence, int amt) throws InterruptedException {
AccountSequence left = this;
AccountSequence right = accountSequence;
if (left.id > right.id) {
left = accountSequence;
right = this;
}
synchronized (left) {
System.out.println(Thread.currentThread().getName() + " this lock");
synchronized (right) {
System.out.println(Thread.currentThread().getName() + " account2 lock");
accountSequence.balance += amt;
this.balance -= amt;
System.out.println(Thread.currentThread().getName() + " account2 unlock");
}
System.out.println(Thread.currentThread().getName() + " this unlock");
}
}
public static void main(String[] args) {
AccountSequence accountSequence = new AccountSequence();
accountSequence.id = 2;
AccountSequence accountSequence2 = new AccountSequence();
accountSequence2.id = 1;
Thread a = new Thread(()->{
try {
accountSequence.transfer(accountSequence2,100);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread b = new Thread(()->{
try {
accountSequence2.transfer(accountSequence,90);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
a.start();
b.start();
}
}
运行结果:避免了死锁哟
四、总结
要想不死锁,要么控制资源顺序,要么打包一起申请。