四死锁以及解决方案

Posted Java码农社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了四死锁以及解决方案相关的知识,希望对你有一定的参考价值。

死锁以及解决方案

一、概念

死锁是两个或两个以上的线程阻塞着等待其他处于死锁状态的线程所持有的锁。

死锁通常发生在多个线程同时但以不同的顺序请求同一组锁的时候。

例如:

线程A锁住了A,然后尝试对B进行加锁。与此同时,线程B已经锁住了B,接着又尝试对A进行加锁。这时候就发生死锁了。线程A永远得不到B对象,线程B也永远得不到A对象,互相僵持。更可怕的是他们永远也不会知道发生了这样的事情,为了得到彼此的对象(A和B),他们会永远阻塞下去。持续消耗着性能。

二、产生死锁的必要条件

1、互斥

某个资源在一段时间内只能由一个进程占有,不能同时被两个或者两个以上的进程占有。

2、不可抢占

进程所获得的资源在未使用完毕之前,资源申请者不能强行从资源占有者手中夺取资源,而只能由该资源占有者自行释放。

3、占有且申请

一个进程因请求资源而阻塞时,对已获得的资源保持不放。

4、循环等待

存在一个进程等待序列{P1,p2,..,pn}其中P1等待P2所占有的资源,P2等待P3占有的资源,.....,而Pn等待P1所占有的资源,形成一个进程循环等待环。

三、死锁Demo

1、简单死锁

package com.ctw.test.aqs;

public class DeadLockDemo {

   private static Object object1 = new Object();
   private static Object object2 = new Object();

   public static void main(String[] args) {
       Thread thread1 = new Thread(new Runnable() {
           @Override
           public void run() {
               synchronized (object1) {
                   try {
                       Thread.sleep(1000);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   synchronized (object2) {
                       System.out.println(Thread.currentThread().getName());
                   }
               }
           }
       });

       Thread thread2 = new Thread(new Runnable() {
           @Override
           public void run() {
               synchronized (object2) {
                   try {
                       Thread.sleep(1000);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   synchronized (object1) {
                       System.out.println(Thread.currentThread().getName());
                   }
               }
           }
       });

       thread1.start();
       thread2.start();
   }

}

2、中级死锁

package com.ctw.test.aqs;

import java.util.ArrayList;
import java.util.List;

public class TreeNodeDeadLockDemo {

   private TreeNodeDeadLockDemo parent;
   private List<TreeNodeDeadLockDemo> children = new ArrayList();

   public synchronized void addChild(TreeNodeDeadLockDemo child) {
       if (! this.children.contains(child)) {
           this.children.add(child);
           child.setParentOnly(this);
       }
   }

   public synchronized void addChildOnly(TreeNodeDeadLockDemo child) {
       if (! this.children.contains(child)) {
           this.children.add(child);
       }
   }

   public synchronized void setParent(TreeNodeDeadLockDemo parent) {
       this.parent = parent;
       parent.addChildOnly(this);
   }

   public synchronized void setParentOnly(TreeNodeDeadLockDemo parent) {
       this.parent = parent;
   }

   public static void main(String[] args) {
       final TreeNodeDeadLockDemo parent = new TreeNodeDeadLockDemo();
       final TreeNodeDeadLockDemo child = new TreeNodeDeadLockDemo();

       Thread thread1 = new Thread(new Runnable() {
           @Override
           public void run() {
               parent.addChild(child);
           }
       });
       Thread thread2 = new Thread(new Runnable() {
           @Override
           public void run() {
               child.setParent(parent);
           }
       });

       thread1.start();
       thread2.start();

   }

}

2.1、分析

  • 有thread1和thread2两个线程

  • thread1调用parent.addChild(child);。因为addChild方法是synchronized的,所以thread1会锁住parent对象。

  • thread2调用child.setParent(parent);。因为setParent方法是synchronized的,所以thread2会锁住child对象。

  • 接下来thread1尝试调用child.setParentOnly()方法,但是由于Child对象现在是被thread2锁住的。所以调用会阻塞(blocked)状态。反之,thread2也尝试调用parent.addChildOnly(),但是由于parent对象现在被thread1锁住,导致thread2也阻塞在该方法处。现在两个线程都被阻塞并等待着获取另外一个线程所持有的锁。

  • 这两个线程需要同时调用parent.addChild(child)和child.setParent(parent)方法,才有可能发生死锁。上面的代码可能运行一段时间才会出现死锁。或者自己多运行几次就出现了。也可能不出现。

3、高级死锁

死锁可能不止包含2个线程,这让检测死锁变得更加困难。下面是4个线程发生死锁的例子:

Thread 1  locks A, waits for B
Thread 2  locks B, waits for C
Thread 3  locks C, waits for D
Thread 4  locks D, waits for A

线程1等待线程2,线程2等待线程3,线程3等待线程4,线程4等待线程1。

4、重入死锁

参考【带你全面了解线程中的各种锁】的【二、可重入锁】的【4、重入锁死】。

5、DB死锁

更加复杂的死锁场景发生在数据库事务中。一个数据库事务可能由多条SQL更新请求组成。当在一个事务中更新一条记录,这条记录就会被锁住避免其他事务的更新请求,直到第一个事务结束。同一个事务中每一个更新请求都可能会锁住一些记录。

当多个事务同时需要对一些相同的记录做更新操作时,就很有可能发生死锁,例如:

Transaction 1, request 1, locks record 1 for update
Transaction 2, request 1, locks record 2 for update
Transaction 1, request 2, tries to lock record 2 for update.
Transaction 2, request 2, tries to lock record 1 for update.

因为锁发生在不同的请求中,并且对于一个事务来说不可能提前知道所有它需要的锁,因此很难检测和避免数据库事务中的死锁。

四、避免死锁

这篇文章写的很好,通俗易懂,我就不复制粘贴了。

http://ifeve.com/deadlock-prevention/



参考文章

https://my.oschina.net/u/3471412/blog/2236883

https://www.ibm.com/developerworks/cn/java/j-lo-deadlock/

http://ifeve.com/deadlock/

http://ifeve.com/deadlock-prevention/

五、广告

https://gitee.com/geekerdream/

参考文章

https://my.oschina.net/u/3471412/blog/2236883

https://www.ibm.com/developerworks/cn/java/j-lo-deadlock/

http://ifeve.com/deadlock/

http://ifeve.com/deadlock-prevention/


以上是关于四死锁以及解决方案的主要内容,如果未能解决你的问题,请参考以下文章

死锁的概念以及处理方式

java 多线程-死锁的产生以及解决方案

并发进阶常见的死锁类型

浅谈死锁问题以及如何解决

Java并发编程实战 04死锁了怎么办?

Java并发编程实战 04死锁了怎么办?