线程安全(二)--死锁

@TOC

一:什么是死锁???

public class Demo1 {
    public static void main(String[] args) {
        Object locker=new Object();

        Thread thread=new Thread(()->{
            synchronized(locker){
                synchronized (locker){
                    System.out.println("hello thread");
                }
            }
        });
        thread.start();
    }
}

上述代码:thread一共加了两次锁,第一次加锁,肯定是能够成功的,但当第二次加锁的时候,此时,第一次加锁后,还未进行解锁操作,那么第二次加锁操作就不能获得锁对象,就会加锁失败,那么该线程就会进入阻塞等待,等待到第一次加锁 操作完了,释放锁操作,但第一次的操作要释放锁,那么必须执行完第二次加锁,解锁操作(代码顺序执行).
这样就会造成第二次加锁操作阻塞等待,等第一次操作释放锁,由于代码顺序执行,第一次要想释放锁,那必须执行完第二次的synchronized操作,那么就非常矛盾,这种情况,就叫做死锁.

二:可重入锁

当我们运行程序的时候,发现代码正常执行了,并没有进入死锁状态.
这是为什么???
是因为synchronized,对上述情况做出处理(JVM).
每个锁对象里,会记录当前是哪个线程持有了这个锁,当针对这个锁对象进行加锁操作的时候,就会先判定一下,当前尝试加锁的线程,是否是持有锁的线程,如果是,直接进行加锁操作,如果不是,那就阻塞等待.这种锁也就作可重入锁

synchronized(locker){
                synchronized (locker){
                    System.out.println("hello thread");
                }//1
            }//2

上面代码,当加了2层锁的时候,代码执行到哪里是要真正的进行解锁操作呢?
肯定是在2这里解锁(最外层的}),否则,如果在1解锁,那么1和2中间有代码的话,是没有被保护起来的.
同理:如果加了N层锁,是如何判定遇到的**}是最外层的呢?
其实,JVM会给锁对象维护一个计数器(int n),每次遇到
{** 就加1,(只有第一次才是真正的加锁),每次遇到**}**,n就-1,当n=0的时候,就是真正的解锁.

三:死锁的典型场景:

1:场景一:不可重入锁

锁是不可重入锁,并且一下线程针对一个锁对象,连续加锁多次,
引入可重入锁,问题就解决了.

2:场景二:两个线程,两把锁

有线程1和线程2,锁A和锁B;现在,线程1拿到了锁A,线程2拿到了锁B,然后线程1想要获取锁B,就需要阻塞等待,等待线程2 释放锁B,同理线程2想要获取到锁A,就需要等待线程1释放锁A,两个线程都进入阻塞等待,那么就会引起死锁问题.

public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        Object locker1=new Object();
        Object locker2=new Object();
        Thread t1=new Thread(()->{
            synchronized(locker1){
                try {
                    Thread.sleep(1000);//sleep()是为了让t2线程拿到另一把锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker2){
                    System.out.println("hello t1");
                }
            }
        });
        Thread t2=new Thread(()->{
            synchronized(locker2){
                synchronized (locker1){
                    System.out.println("hello t2");
                }
            }
        });
        t1.start();

        t2.start();
    }
}

此时:t1 尝试对locker2加锁,就会阻塞等待,等待t2释放locker2;t2尝试对locker1加锁,也会阻塞等待,等待t1释放locker1.
在这里插入图片描述
在这里插入图片描述
代码执行结果:进程没有退出,也没有打印任何的内容,死锁了.
通过jconslole可以看到这里线程的状态:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3:场景三:N个线程,M把锁

哲学家就餐问题:
每个滑稽都坐在两个筷子之间.每个滑稽要做两件事:
1:思考人生(放下筷子),
2:吃面条(拿起左右两根筷子)
每个哲学家什么时候吃面条,什么时候思考人生,都是不确定的(线程抢占式执行)
那么就会出现极端情况,同一时刻,所以的滑稽都拿起左边的筷子,此时,所以的滑稽都无法拿起右边的筷子,并且每个滑稽都是固执的人(每个哲学家只有吃不到面条,绝不会放下手中的筷子),那么就会引起死锁问题

四:如何解决死锁问题???

死锁,是非常严重的问题,就会使线程阻塞等待,使线程卡住了,没法正常工作了,更可怕的是,死锁这种bug,往往都是概率性出现.
那么就必须解决死锁问题,同时线程安全问题也必须解决.

4.1:死锁的4个必要条件

1:锁具有互斥特性(锁的基本特点,一个线程拿到锁之后,其他线程要想拿到这个锁,就必须阻塞等待).
2:锁不可抢占(不可被剥夺):一个线程拿到锁之后,除非它自己主动释放锁,否则其他线程抢不走(锁的基本特点).
3:请求和保持(嵌套锁):一个线程拿到一把锁之后,不释放这个锁的前提下,再尝试获取别的锁.
4:循环等待:多个线程获取多个锁的过程中,出现了循环等待.A线程等待B,B线程等待A.
若要构成死锁,这4个条件缺一不可,那么如果要解决死锁问题,就要从这四个条件下手.而条件1和条件2,是锁的基本特性,无法修改.

4.2:从条件3解决死锁问题:

public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        Object locker1=new Object();
        Object locker2=new Object();
        Thread t1=new Thread(()->{
            synchronized(locker1){
                try {
                    Thread.sleep(1000);//sleep()是为了让t2线程拿到另一把锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
            synchronized (locker2){
                System.out.println("hello t1");
            }
        });
        Thread t2=new Thread(()->{
            synchronized(locker2){

            }
            synchronized (locker1){
                System.out.println("hello t2");
            }
        });
        t1.start();

        t2.start();
    }
}

在这里插入图片描述
但有的情况下,嵌套锁的情况必须存在,那么只好用另一种方法了.

4.3:从条件4解决死锁问题:

public class Demo3 {
    public static void main(String[] args) {
        Object locker1=new Object();
        Object locker2=new Object();
        Thread t1=new Thread(()->{
            synchronized (locker1){
                synchronized (locker2){
                    System.out.println(" t1获得了两把锁");
                }
            }

        });
        Thread t2=new Thread(()->{
            synchronized (locker1){
                synchronized (locker2){
                    System.out.println(" t2获得了两把锁");
                }
            }

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

    }
}

在这里插入图片描述

相关推荐

  1. 【问题记录】线问题

    2024-03-31 01:16:02       27 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-31 01:16:02       91 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-31 01:16:02       97 阅读
  3. 在Django里面运行非项目文件

    2024-03-31 01:16:02       78 阅读
  4. Python语言-面向对象

    2024-03-31 01:16:02       88 阅读

热门阅读

  1. 对象数组与指针与引用

    2024-03-31 01:16:02       41 阅读
  2. css之flex布局文本不换行不显示省略号的解决方法

    2024-03-31 01:16:02       45 阅读
  3. 09、Lua 运算符

    2024-03-31 01:16:02       43 阅读
  4. SpringMVC源码分析(六)--参数名称解析器

    2024-03-31 01:16:02       36 阅读
  5. Web框架开发-Django-form组件

    2024-03-31 01:16:02       42 阅读