概念:当一个线程永远的持有一个锁,并且其他线程都尝试获得这个锁时,那么它们将永远被阻塞
锁顺序死锁(抱死)
/**
* 锁顺序死锁
*/
public class LeftRightDeadLock {
private static Object left = new Object();
private static Object right = new Object();
public static void leftRight() {
synchronized (left) {
//添加延迟时间来模拟锁获取冲突
synchronized (right) {
System.err.println("leftRight::");
}
}
}
public static void rightLeft() {
synchronized (right) {
//添加延迟时间来模拟锁获取冲突
synchronized (left) {
System.err.println("rightLeft::");
}
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newFixedThreadPool(2);
for (int i = 0; i < 1000000; i++) {
exec.execute(()->leftRight());
exec.execute(()->rightLeft());
}
exec.shutdown();
exec.awaitTermination(10000,TimeUnit.SECONDS);
}
}
原理图
两个线程试图以不同的顺序获得相同的锁,如果按照相同的顺序来请求锁,那么就不会出现循环依赖的加锁依赖性,因此也就不会产生死锁。
如果线程都按照固定的顺序来获得锁,那么在程序中就不会出现锁顺序死锁的问题
动态的锁顺序死锁
还有一种情况是,取决于参数顺序而导致的锁顺序不同,也会出现锁顺序死锁
/**
* 动态锁顺序死锁
*/
public class DynamicLeftRightDeadLock {
/**
* 转账问题
* @param fromAccount 来源账户
* @param toAccount 目标账户
* @param amount 处理额度
*/
public static void transferMoney(AtomicInteger fromAccount,
AtomicInteger toAccount,
Integer amount) {
synchronized (fromAccount) {
synchronized (toAccount) {
System.err.println("in::");
//该值小于参数
if (Integer.valueOf(fromAccount.get()).compareTo(amount)<0) {
throw new RuntimeException("");
}else {
fromAccount.addAndGet(-amount);
toAccount.addAndGet(amount);
}
}
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newFixedThreadPool(2);
AtomicInteger a = new AtomicInteger(100);
AtomicInteger b = new AtomicInteger(100);
for (int i = 0; i < 1000000; i++) {
exec.execute(()->transferMoney(a,b,1));
exec.execute(()->transferMoney(b,a,1));
}
exec.shutdown();
exec.awaitTermination(10000, TimeUnit.SECONDS);
}
}
原理同上
协作死锁
协作对象持有锁的时候,调用外部的方法,那么将会出现活跃性问题。因为外部方法可能会获得其他锁(有可能就会产生死锁),如果阻塞时间过长,也会导致其他线程无法获取被当前线程持有的锁
/**
* 协作死锁
*/
public class CooperationDeadLock {
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newFixedThreadPool(2);
Dispatcher dispatcher = new Dispatcher();
// 模拟五辆车反复进入调度
Taxi[] taxis = new Taxi[5];
for (int i = 0; i < 5; i++) {
taxis[i] = new Taxi(dispatcher);
}
for (int i = 0; i < 100; i++) {
//两个对象协作执行时,容易产生死锁
exec.execute(()->{
int index = new Random().nextInt(5);
taxis[index].setDistance(0);
});
exec.execute(()->dispatcher.getSnapshot());
}
exec.shutdown();
exec.awaitTermination(5, TimeUnit.SECONDS);
}
}
/**
* 出租车调度系统,调度获取出租车信息
*/
class Taxi {
private int distance;
private final Dispatcher dispatcher;
Taxi(Dispatcher dispatcher) {
this.dispatcher = dispatcher;
}
public synchronized int getDistance() {
return distance;
}
public synchronized void setDistance(int distance) {
this.distance = distance;
if (distance==0) {
//外部锁
dispatcher.notifyAvailable(this);
}
}
}
class Dispatcher {
private Set<Taxi> availableTaxis;
public Dispatcher() {
availableTaxis = new HashSet<>();
}
public synchronized void notifyAvailable(Taxi taxi) {
availableTaxis.add(taxi);
}
// 获取当前可调度车辆快照
public synchronized String getSnapshot() {
StringBuilder builder = new StringBuilder();
for (Taxi taxi: availableTaxis) {
//外部锁
builder.append(taxi+": "+taxi.getDistance()+"\n");
}
return builder.toString();
}
}
原理图
同上,不同在于不是显式的使用双重锁,而是在持有锁的方法中调用了外部锁
建议使用开放调用:如果在调用某个方法的时候不需要持有锁,那么就叫做开放调用;将锁的范围控制在方法内部,具体如下
public void setDistance(int distance) {
boolean isArrive;
synchronized(this) {
this.distance = distance;
isArrive = distance==0;
}
if (isArrive) {
//外部锁
dispatcher.notifyAvailable(this);
}
}
本内容示例主要来自《Java并发编程实战》