并发与多线程【二】——死锁

引言

多线程协作时,因为对资源的锁定与等待会产生死锁,在明白死锁概念后需要了解死锁产生的四个基本条件,明白竞争条件和临界区的概念,还需要知道通过破坏造成死锁的4个条件来防止死锁。

下面对死锁概念、死锁的产生原因、死锁的四个必要条件等内容作记录。

在描述死锁概念之前先理解竞态条件和临界区两个概念。

竞态条件

静态条件:如果程序运行顺序的改变会影响最终结果,这就是一个竞态条件(race condition)。从这句话来看“竞态条件”似乎是一种“现象”——由于程序运行顺序的不同造成得到最终结果的不同这样的一种现象,称这种现象为“竞态条件”。

举个被大家用烂了的例子:

如果一段程序运行多次的结果不一致(排除生成随机数的情况),那这就可能是竞态条件的体现。比如最典型的例子,两个线程同时把一个类的静态成员做50次自增加1的操作,即:

i++

写在两个线程中,都运行50次,运行结束以后用主线程去取这个变量的值几乎不可能是100. 有的时候是97,有的时候是98,这是用来说明竞态条件的最有效例子。

自增加操作其实是三个操作的组合: 

  1. 取该变量的值
  2. 给这个取到的值+1
  3. 把计算好的值赋给该变量

学过计算机系统的同学会知道这三个操作的区别,取值是从内存取到寄存器,+1以后值还是在寄存器,只有在赋值完成后,内存中的该变量的值才会变化。

我们的竞态条件发生的原因就是在一个+1的值还没有赋给变量的时候,另一个线程开始读取内存中的该变量的值,等这个线程完成+1、赋值以后,他的工作其实和之前那个线程是一样的,有若干类似现象出现以后,就会导致最后的值永远达不到100。

临界区

什么是死锁?

深入理解计算机系统书中是这样定义死锁的:信号量引入了一种潜在的运行时错误,叫做死锁(deadlock),它指的是一组线程被阻塞了,等待一个永远也不会为真的条件。并用进度图对死锁进行了描述和讲解。

死锁 : 当线程A持有独占锁a,并尝试去获取独占锁b的同时,线程B持有独占锁b,并尝试获取独占锁a的情况下,就会发生AB两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。

四所产生的四个基本条件

  1. 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
  2. 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
  4. 环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链。

如何检测死锁?

要想检测死锁,先得创造一个死锁。

package com.xgcd.concurrent;

public class DeadLockTest {
    public static void main(String[] args) {

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                DeadLockTest.method1();
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                DeadLockTest.method2();
            }
        });

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

    public static void method1() {
        // synchronized(lockA)
        synchronized (String.class) {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程a尝试获取Integer.class...");
            // synchronized(lockB)
            synchronized (Integer.class) {
                System.out.println("线程a获取Integer.class success...");
            }
        }
    }

    public static void method2() {
        synchronized (Integer.class) {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程b尝试获取String.class...");
            synchronized (String.class) {
                System.out.println("线程b获取String.class success...");
            }
        }
    }
}

运行main方法后,执行结果:

1、JConsole工具

此时通过 JDK 自带的工具 jConsole 来检测死锁的存在,命令行中输入 jconsole,调出工具:

 

 

 2、Jstack工具

jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息。

Jstack工具可以用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿

的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。

如何破坏死锁?

感谢

https://blog.csdn.net/Clifnich/article/details/78447524

https://blog.csdn.net/jonnyhsu_0913/article/details/79633656

猜你喜欢

转载自www.cnblogs.com/yadongliang/p/12325577.html
今日推荐