例子1: 锁顺序引发的死锁
private final Object left = new Object();
private final Object right = new Object();
public void leftRigth(){
synchronized (left){
synchronized (right){
System.out.println("leftRigth");
}
}
}
public void rightLeft(){
synchronized (right){
synchronized (left){
System.out.println("rightLeft");
}
}
}
启用两个线程,一个调用leftRight()方法,一个调用rightLeft()方法,并且这两个线程交错的执行,它们就会发生死锁。
例子2:单线程死锁
public static void main(String[] args){
RenderPageTask renderPageTask = new RenderPageTask();
renderPageTask.executor.submit(renderPageTask);
}
:
public class RenderPageTask implements Callable<String> {
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("wxAuth-pool-%d").build();
public ExecutorService executor = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),threadFactory);
@Override
public String call() throws ExecutionException, InterruptedException {
System.out.println("进入了。。");
Future<String> header;
header = (Future<String>) executor.submit(()->{System.out.println("22");});
return header.get();
}
}
使用单线程的线程池,提交一个任务A到线程池,然后再执行A这个任务的过程中在给线程池提交一个任务B。由于是单线程的线程池,所以任务B的执行需要等待任务A执行完才能获得线程。但是A的执行完毕又依赖B,所以就形成了死锁。
例子3:动态的锁顺序死锁
在例子1中,锁的顺序是固定的。在实际的操作过程中可能锁时动态的,比如支付场景中,x向y转钱,这个过程需要时原子性的,所以需要对金额的计算进行加锁。这个过程要对x与y的金额都要上锁,所以有两个锁。但是,可能有一个线程从x向y转账,一个线程从y向x转账,这样就形成了动态的死锁。要避免这样动态锁的顺序造成的死锁问题,可以指定锁的顺序,使用Object.hashCode返回的值去决定先加那个锁,让每次锁的顺序都是一样的。比如前面的例子,不管是x向y转钱还是y向x转钱他们上锁的顺序都是一样的。可能都是先给x加锁。
但是,也有可能两个对象拥有相同的散列值,这时必须通过某种任意的方法来决定锁的顺序,但是这可能又引入死锁。为了避免这种情况,可以使用“加时赛”锁。
参考资料:
《java并发编程实战》