什么是死锁,如何避免死锁?Java死锁详解

什么是死锁

死锁是指多个进程因竞争资源而引起的一种僵局,如果没有外力作用,所有进程都不会向前推进。
举例:假如有线程Thread1和Thread2,两个都要访问共享资源AB,Thread1和Thread2一个先访问A再访问B,另一个先访问B再访问A。但在他们线程未执行完时,都不会释放AB资源,那么就形成了一种僵局,Thread1在等待Thead2施放B资源,Thread2在等待Thread1释放A资源,两个进程就形成了一种僵局,下面以Java代码实现为例说明:


public class DeadThread {

    private Thread thread1;


    public DeadThread(Object object1,Object object2){

        thread1 = new Thread(() -> {

                synchronized (object1){
                    System.out.println("获取到o1"+Thread.currentThread().getName());
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                        synchronized (object2){
                System.out.println("获取到o2"+Thread.currentThread().getName());
            }
                }
        


    });
        thread1.start();
    }

    public static void main(String[] args) {
        Object object1=new Object();
        Object object2=new Object();
        DeadThread deadThread=new DeadThread(object1,object2);
        DeadThread deadThread1=new DeadThread(object2,object1);


    }

}

其中Object1和Object2就是上述的资源AB。

死锁产生的必要条件

  1. 互斥条件:资源互斥访问。一个资源每次只能被一个进程使用,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
  2. 请求保持:进程已经拥有了资源,又请求资源。进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
  3. 不可剥夺:进程拥有的资源不会被剥夺,只能自己主动释放。进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
  4. 循环等待: 进程之间形成了一个循环等待资源的环。若干进程间形成首尾相接循环等待资源的关系。
    死锁产生的原因

死锁产生的原因

进程推进顺序非法。
进程竞争资源。

Java中怎么监测死锁

还是以上述程序为例:可以看到这两个线程都处于blocked状态
用idea自带的debug功能
在这里插入图片描述
在这里插入图片描述
或者用jconsole工具来查看(未使用过)

操作系统中如何避免或打破死锁

破坏死锁的必要条件,互斥条件是不可变的,只能想办法打破后三个条件。
比如破坏请求保持条件,进程必须一次性申请所需的全部资源。还有著名的银行家算法。等等,这里不作详细介绍

Java中如何避免或打破死锁

根据Java核心技术卷I中的说法,**在Java语言中没有任何东西可以避免或打破这种死锁现象,必须仔细设计程序,以确保不会出现死锁。**言下之意即,程序员在设计线程时,必须想办法尽量避免死锁现象。

后来,查阅资料发现jdk1.5后新增了一个类可用于侦察死锁,见下文Reentranlock处:

程序输出

在这里插入代码片获取到锁2135218919Thread-1
获取到锁131519948Thread-0
---[J@6d311334
tsun.misc.Unsafe.park(Native Method)
tjava.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
tjava.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
tjava.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
tjava.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
tjava.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
tjava.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
tDeadThread1.lambda$new$0(DeadThread1.java:27)
tDeadThread1$$Lambda$1/1096979270.run(Unknown Source)
tjava.lang.Thread.run(Thread.java:748)
tsun.misc.Unsafe.park(Native Method)
tjava.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
tjava.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
tjava.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
tjava.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
tjava.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
tjava.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
tDeadThread1.lambda$new$0(DeadThread1.java:27)
tDeadThread1$$Lambda$1/1096979270.run(Unknown Source)
tjava.lang.Thread.run(Thread.java:748)

可以看到可以快速定位到死锁发生的代码处
①避免锁嵌套

尽量不要使用锁嵌套
如果已经持有一个资源,请避免锁定另一个资源。
这是打破了请求保持条件

②合理的进程推进顺序和合理的加锁顺序

③尽量只锁定必要资源,不要对方法加锁,对方法块加锁
④避免无限期等待
如下列代码

public class DeadJoinExample {
    private class A extends Thread{
        private B b;
        public void setB(B b) {
            this.b = b;
        }


        A(){}
        A(B b){
            this.b=b;
        }
        @Override
        public void run() {
            try {


                b.join(1000);
                //设置超时时间,避免无线等待

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("A");

        }

    }
    private class B extends Thread {
        private A a;
        B(A a) {
            this.a = a;
        }
        @Override
        public void run() {
            try {

                a.join();

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("B");
        }
    }
    public void test() {
        A a = new A();
        B b = new B(a);
        a.setB(b);
        a.start();
        b.start();

    }

    public static void main(String[] args) throws InterruptedException {
        DeadJoinExample example = new DeadJoinExample();
        example.test();



    }

}

⑤ReentrantLock提供了lock()、tryLock()、tryLock(long timeout, TimeUnit unit)、lock.lockInterruptibly()

  • lock()
    当锁不可用,那么当前线程被阻塞,休眠一直到该锁可以获取,

  • trylock()

    当获取锁时,如果其他线程持有该锁,无可用锁资 源,直接返回false,这时候线程不用阻塞等待,可以先去做其他事情;

  • tryLock(long timeout, TimeUnit unit)

    当获取锁时,在超时等待时间之内,被中断了,那么抛出InterruptedException,不再继续等待

  • lockInterruptibly()
    当获取锁时,锁资源不可用,那么该线程开始阻塞休眠等待,但是等待过程中如果有中断事件,那么会停止等待,立即返回.(也就是可以响应中断)

使用ReentrantLock也可以有效避免死锁

示例Demo

public class DeadThread1 {

    private Thread thread1;
    private  ReentrantLock reentrantLock;
    private  ReentrantLock reentrantLock1;



    public DeadThread1(ReentrantLock reentrantLock,ReentrantLock reentrantLock1){
        this.reentrantLock=reentrantLock;
        this.reentrantLock1=reentrantLock1;
        thread1 = new Thread(() -> {

            reentrantLock.lock();
                System.out.println("获取到锁1"+Thread.currentThread().getName());
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                reentrantLock1.lock();
                    System.out.println("获取到锁2"+Thread.currentThread().getName());
                 reentrantLock1.unlock();

            reentrantLock.unlock();


        });
        thread1.start();
    }

    public static void main(String[] args) {
        ReentrantLock reentrantLock=new ReentrantLock();
        ReentrantLock reentrantLock1=new ReentrantLock();
        DeadThread1 deadThread=new DeadThread1(reentrantLock,reentrantLock1);
        DeadThread1 deadThread1=new DeadThread1(reentrantLock1,reentrantLock);


    }

}


以上是使用正常锁方法,lock(),线程阻塞
在这里插入图片描述
修改一下改成tryLock()

import java.util.concurrent.locks.ReentrantLock;

public class DeadThread1 {

    private Thread thread1;
    private  ReentrantLock reentrantLock;
    private  ReentrantLock reentrantLock1;



    public DeadThread1(ReentrantLock reentrantLock,ReentrantLock reentrantLock1){
        this.reentrantLock=reentrantLock;
        this.reentrantLock1=reentrantLock1;
        thread1 = new Thread(() -> {

            reentrantLock.lock();
                System.out.println("获取到锁"+reentrantLock.hashCode()+Thread.currentThread().getName());
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                reentrantLock1.tryLock();
                //查看r锁是否是当前线程拥有
                    System.out.println("获取到锁"+reentrantLock1.hashCode()+"了吗?"+reentrantLock1.isHeldByCurrentThread()+"  "+Thread.currentThread().getName());

            reentrantLock.unlock();


        });
        thread1.start();
    }

    public static void main(String[] args) {
        ReentrantLock reentrantLock=new ReentrantLock();
        ReentrantLock reentrantLock1=new ReentrantLock();
        DeadThread1 deadThread=new DeadThread1(reentrantLock,reentrantLock1);
        DeadThread1 deadThread1=new DeadThread1(reentrantLock1,reentrantLock);


    }

}


在这里插入图片描述
lockInterruptibly方法中断死锁

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

public class DeadThread1 {

    private Thread thread1;
    private  ReentrantLock reentrantLock;
    private  ReentrantLock reentrantLock1;



    public DeadThread1(ReentrantLock reentrantLock,ReentrantLock reentrantLock1){
        this.reentrantLock=reentrantLock;
        this.reentrantLock1=reentrantLock1;
        thread1 = new Thread(() -> {

            reentrantLock.lock();
                System.out.println("获取到锁"+reentrantLock.hashCode()+Thread.currentThread().getName());

            try {
                //可中断得锁
                Thread.sleep(1000);
                reentrantLock1.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //查看r锁是否是当前线程拥有

                    System.out.println("获取到锁"+reentrantLock1.hashCode()+"了吗?"+reentrantLock1.isHeldByCurrentThread()+"  "+Thread.currentThread().getName());
                 reentrantLock1.unlock();

            reentrantLock.unlock();


        });
        thread1.start();
    }

    public static void main(String[] args) throws InterruptedException {
        ReentrantLock reentrantLock=new ReentrantLock();
        ReentrantLock reentrantLock1=new ReentrantLock();
        DeadThread1 deadThread=new DeadThread1(reentrantLock,reentrantLock1);
        DeadThread1 deadThread1=new DeadThread1(reentrantLock1,reentrantLock);
        //主线程睡眠是为了让死锁先发生
        Thread.sleep(4000);
        System.out.println("主线程睡眠结束");
        ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
        //查找发生死锁的线程,返回线程id的数组
        long[] deadLockThreadIds = mbean.findDeadlockedThreads();
        System.out.println("---" + deadLockThreadIds);
      if (deadLockThreadIds != null) {
            //获取发生死锁的线程信息
            ThreadInfo[] threadInfos = mbean.getThreadInfo(deadLockThreadIds);
            //获取JVM中所有的线程信息
            Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
            for (Map.Entry<Thread, StackTraceElement[]> entry : map.entrySet()) {
                for (int i = 0; i < threadInfos.length; i++) {
                    Thread t = entry.getKey();
                    if (t.getId() == threadInfos[i].getThreadId()) {
                        //中断发生死锁的线程
                        t.interrupt();//线程会相应中断
                        //打印堆栈信息
                        /* for (StackTraceElement ste : entry.getValue()) {
                        System.err.println("t" + ste.toString().trim());
                         }*/
                    }

                }
            }
        }
    }

}


程序输出:

获取到锁2135218919Thread-1
获取到锁131519948Thread-0
主线程睡眠结束
---[J@6d311334
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at DeadThread1.lambda$new$0(DeadThread1.java:26)
	at java.lang.Thread.run(Thread.java:748)
获取到锁2135218919了吗?false  Thread-0
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at DeadThread1.lambda$new$0(DeadThread1.java:26)
	at java.lang.Thread.run(Thread.java:748)
获取到锁131519948了吗?false  Thread-1
Exception in thread "Thread-0" Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
	at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
	at DeadThread1.lambda$new$0(DeadThread1.java:33)
	at java.lang.Thread.run(Thread.java:748)
java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
	at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
	at DeadThread1.lambda$new$0(DeadThread1.java:33)
	at java.lang.Thread.run(Thread.java:748)

线程被中断不一定意味着线程停止,如何响应中断,取决于线程本身,与其自身写法也有关。Java核心技术卷I 章节14.2中有提及。

部分参考自:https://www.cnblogs.com/liukaifeng/p/10052646.html
https://blog.csdn.net/michaelgo/article/details/81481068
https://www.jianshu.com/p/120872f406f3?from=singlemessage

猜你喜欢

转载自blog.csdn.net/weixin_44137260/article/details/106049659