多线程并发 (六) 了解死锁

总结:

  1. 多线程并发 (一) 了解 Java 虚拟机 - JVM 学习知道了创建一个线程会触发JVM创建一个私有的虚拟机栈、程序计数器,当前的虚拟机栈内存就是当前线程的运行内存,程序计数器就是记录当前线程运行的代码的地址。
  2. 多线程并发 (二) 了解 Thread 学习了线程的运行状态、线程的创建方式、调度原理等。
  3. 多线程并发 (三) 锁 synchronized、volatile 学习知道了多个线程在竞争资源的时候产生的影响、怎么解决并发问题、synchronized的用法、synchronized锁的升级、synchronzied原理为什么锁的是Object对象、Object对象在内存的分布、volatile解决的问题以及错误使用。
  4. 多线程并发 (四) 了解原子类 AtomicXX 属性地址偏移量,CAS机制 学习知道了原子类型的源码实现、对象地址偏移量的理解、CAS机制。
  5. 多线程并发 (五) ReentrantLock 使用和源码 学习知道了 ReentrantLock 的用法、ReentrantLock类型、源码实现类、原理AQS的队列操作。

经过对以上章节的学习,对于正常并发项目遇到问题应该都能解决了,并且学习了锁的原理,线程的运行状态,调度方式等。接下来就引入使用锁来解决并发的同时锁带来的问题 ‘死锁’。

1.什么是死锁

百度百科说死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

简单举例如果线程A锁住了记录1并等待记录2,而线程B锁住了记录2并等待记录1,这样两个线程就发生了死锁现象。

2.死锁产生的条件

从这个例子中线程A锁住了记录1并等待记录2,而线程B锁住了记录2并等待记录1,这样两个线程就发生了死锁现象。我们能得到一些条件

  1. 互斥条件:同一时间内资源只能被一个线程访问即 A 占用了记录1,B就不能占用。
  2. 不剥夺条件:即A 在占用记录1时候,其他线程是无法强制让A 释放资源的,必须等待
  3. 请求并保持条件:即A 在占用记录1时候,还去申请记录2
  4. 循环等待条件:即A 在占用了记录1后,继续申请记录2,直到申请到记录2为止,否则一直申请

死锁 好似 生活中的杠精,自己有老婆还要去找别人老婆,那别人怎么能放过你,不把你打死。
死锁就像婚姻,生活中的例子:
主演:黄脚明、痒颖baby、刘开胃、杨咪咪、王藕。
剧情:在一个月黑风高的夜晚,黄脚明、痒颖baby结婚了,刘开胃、杨咪咪结婚了。在经历了生活的洗礼之后黄脚明和刘开胃两个骚年同时看上了他们对方的老婆,由于他们两个都护着自己老婆,导致别人想得也得不到,最终刘开胃放弃了,但是他看上了隔壁村的王藕,王藕还是个单身汗,所以没到月黑风高的夜晚刘开胃就去找王藕谈剧本。 

           搞黄色 原谅我的低俗了。。。。。。。。

3.死锁实例

public class DeadLock implements Runnable{
    
    private static Object obj1 = new Object();
    private static Object obj2 = new Object();
    private boolean flag;
    
    public DeadLock(boolean flag){
        this.flag = flag;
    }
    
    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName() + "运行");
        
        if(flag){
            synchronized(obj1){
                System.out.println(Thread.currentThread().getName() + "已经锁住obj1");
                try {  
                    Thread.sleep(1000);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                synchronized(obj2){
                    // 执行不到这里
                    System.out.println("1秒钟后,"+Thread.currentThread().getName()
                                + "锁住obj2");
                }
            }
        }else{
            synchronized(obj2){
                System.out.println(Thread.currentThread().getName() + "已经锁住obj2");
                try {  
                    Thread.sleep(1000);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                synchronized(obj1){
                    // 执行不到这里
                    System.out.println("1秒钟后,"+Thread.currentThread().getName()
                                + "锁住obj1");
                }
            }
        }
    }

}


package com.demo.test;

public class DeadLockTest {

     public static void main(String[] args) {
         
         Thread t1 = new Thread(new DeadLock(true), "线程1");
         Thread t2 = new Thread(new DeadLock(false), "线程2");

         t1.start();
         t2.start();
    }
}

运行结果:
线程1运行
线程1已经锁住obj1
线程2运行
线程2已经锁住obj2
摘学于:https://www.cnblogs.com/xiaoxi/p/8311034.html

一个简单的死锁类 t1先运行,这个时候flag==true,先锁定obj1,然后睡眠1秒钟而t1在睡眠的时候,另一个线程t2启动,flag==false,先锁定obj2,然后也睡眠1秒钟t1睡眠结束后需要锁定obj2才能继续执行,而此时obj2已被t2锁定t2睡眠结束后需要锁定obj1才能继续执行,而此时obj1已被t1锁定t1、t2相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁。 

    从这个例子也可以反映出,死锁是因为多线程访问共享资源,由于访问的顺序不当所造成的,通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。

4.死锁解决方式

  1. 加锁顺序
    有A、B、C三个线程按照一定的顺序一次获取锁a、b、c,因为如果ABC必须从a开始获取锁然后才能获取b,获取c,只能先获取a锁的线程才能获取b、c。让线程以一定的顺序拿锁。但是这是要求在某种特定的场景,一般不适用。

  2. 加锁时限
    在获取锁的时候,可以尝试获取锁tryLock(timeout)/tryLock()加一个超时时限,如果超过了设置的时间,依然没有得到锁,如果自己已经占有了一个锁,那就释放自己的锁回退。这种方式针对ReentrantLock加锁方式适用,对于synchronzied内部锁是不适用的。

  3. 死锁检测
    死锁检测是一个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景。

        每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph等等)将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。当一个线程请求锁失败时,这个线程可以遍历锁的关系图看看是否有死锁发生。例如,线程A请求锁7,但是锁7这个时候被线程B持有,这时线程A就可以检查一下线程B是否已经请求了线程A当前所持有的锁。如果线程B确实有这样的请求,那么就是发生了死锁(线程A拥有锁1,请求锁7;线程B拥有锁7,请求锁1)。

        当然,死锁一般要比两个线程互相持有对方的锁这种情况要复杂的多。线程A等待线程B,线程B等待线程C,线程C等待线程D,线程D又在等待线程A。线程A为了检测死锁,它需要递进地检测所有被B请求的锁。从线程B所请求的锁开始,线程A找到了线程C,然后又找到了线程D,发现线程D请求的锁被线程A自己持有着。这是它就知道发生了死锁。

下一篇学习线程池

发布了119 篇原创文章 · 获赞 140 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/WangRain1/article/details/104014283