什么是死锁?

x33g5p2x  于2022-03-05 转载在 其他  
字(2.8k)|赞(0)|评价(0)|浏览(339)

概述

多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

如图:两个或以上的进程因为争夺资源而造成互相等待资源的现象称为死锁。

死锁的四个条件

死锁产生的四个必要条件

1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

注意:当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。

具体死锁的操作代码实列

可理解背下来,大厂面试可考,死锁的简单案例

public class DeadLock {

    //创建两个对象
    static Object a = new Object();
    static Object b = new Object();

    public static void main(String[] args) {
        new Thread(()->{
            synchronized (a) {
                System.out.println(Thread.currentThread().getName()+" 持有锁a,试图获取锁b");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b) {
                    System.out.println(Thread.currentThread().getName()+" 获取锁b");
                }
            }
        },"A").start();

        new Thread(()->{
            synchronized (b) {
                System.out.println(Thread.currentThread().getName()+" 持有锁b,试图获取锁a");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (a) {
                    System.out.println(Thread.currentThread().getName()+" 获取锁a");
                }
            }
        },"B").start();
    }
}

验证是否是死锁

  1. jps 类似于linux中的ps -ef查看进程号
  2. jstack jvm自带的堆栈跟踪工具

jps和jstack工具位于JDK文件夹的bin目录下

运行命令jps -l或者jps可以查看所有Java进程的PID

区别:

jps -l:显示类的全限定名称
jps:显示类名

由于我们创建的类名为DeadLock,我们可以直接根据类名来判断

此时,可以看到DeadLock类的PID为20812

然后使用堆栈跟踪工具jstack+进程号PID来查看结果
jstack [进程id] > [导出文件存储的位置]

Java stack information for the threads listed above:
===================================================
"A":
	at com.wk.concurrency.demo.DeadLock.lambda$deadLock$1(DeadLock.java:27)
	- waiting to lock <0x00000007412cd8d0> (a java.lang.String)
	- locked <0x00000007412cd900> (a java.lang.String)
	at com.wk.concurrency.demo.DeadLock$$Lambda$2/804581391.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:745)
"B":
	at com.wk.concurrency.demo.DeadLock.lambda$deadLock$0(DeadLock.java:20)
	- waiting to lock <0x00000007412cd900> (a java.lang.String)
	- locked <0x00000007412cd8d0> (a java.lang.String)
	at com.wk.concurrency.demo.DeadLock$$Lambda$1/6738746.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:745)

Found 1 deadlock.

分析结果

synchronized 释放锁的时机

任何线程进入同步代码块、同步方法之前,必须获得同步监视器的锁定,那么何时会释放这个锁定呢?在程序中,是无法显式释放对同步监视器的锁的,而会在如下几个情况下释放锁。

  1. 当前线程的同步方法、代码块执行结束的时候释放。
  2. 当前线程在同步方法、同步代码块中遇到break 、 return 终于该代码块或者方法的时候释放。
  3. 出现未处理的error或者exception导致异常结束的时候释放。
  4. 程序执行了 同步对象 wait 方法 ,当前线程暂停,释放锁

在以下两种情况不会释放锁

  1. 代码块中使用了 Thread.sleep() Thread.yield() 这些方法暂停线程的执行,不会释放。
  2. 线程执行同步代码块时,其他线程调用 suspend 方法将该线程挂起,该线程不会释放锁 ,所以我们应该避免使用 suspend 和 resume 来控制线程 。

注意

  1. 对于一个已经竞争到同步锁的线程,在还没有走出同步块的时候,即使时间片结束也不会释放锁。
  2. 对象锁和类锁是两个不同的锁。在同一个类的静态方法和实例方法上都加synchronized关键字,这时静态方法的synchronized对应的是 类锁,实例方法的synchronized是对象锁。这是两个不同的锁。 实例对象调用类锁方法也会同步。

volatile关键字的使用

  1. 关键字volatile的主要作用是使变量在多个线程间可见。
  2. 关键字volatile的作用是强制用公共堆栈中取的变量的值,而不是从线程私有的数据栈中取得变量的值。

如何防范死锁?

1.尽量避免使用多个锁(如果有可能的话)。

2.规范的使用多个锁,并设计好锁的获取顺序。

3.随用随放。即是,手里有锁,如果还要获得别的锁,必须释放全部资源才能各取所需。

4.规范好循环等待条件。比如,使用超时循环等待,提高程序可控性

相关文章