如何解决Java多线程死锁问题

死锁问题

死锁定义

多线程编程中,因为抢占资源造成了线程无限等待的情况,此情况称为死锁

死锁举例

注意:线程和锁的关系是:一个线程可以拥有多把锁,一个锁只能被一个线程拥有。

当两个线程分别拥有一把各自的锁之后,又尝试去获取对方的锁,这样就会导致死锁情况的发生,具体先看下面代码:

/**
 * 线程死锁问题
 */
public class DeadLock {
    public static void main(String[] args) {
        //创建两个锁对象
        Object lock1 = new Object();
        Object lock2 = new Object();

        //创建子线程
        /*
        线程1:①先获得锁1 ②休眠1s,让线程2获得锁2 ③线程1尝试获取锁2 线程2同理
         */
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                //线程1业务逻辑
                synchronized(lock1){
                    System.out.println("线程1得到了锁子1");
                    try {
                        //休眠1s,让线程2先得到锁2
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程1尝试获取锁2...");
                    synchronized(lock2){
                        System.out.println("线程1获得了锁2!");
                    }
                }
            }
        },"线程1");

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                //线程2业务逻辑
                synchronized(lock2){
                    System.out.println("线程2得到了锁子2");
                    try {
                //休眠1s,让线程1先得到锁1;因为线程是并发执行我们不知道谁先执行
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程2尝试获取锁1...");
                    synchronized(lock1){
                        System.out.println("线程2获得了锁1");
                    }
                }
            }
        },"线程2");
        thread1.start();
        thread2.start();
    }
}

程序运行结果如下:

可以看出,线程1尝试获取了锁2,线程2尝试获取了锁1,但是二者并没有获取到对方的锁;这就发生了所谓的“死锁”!

如何排查死锁

想要排查死锁具体细节,可以通过三个工具(位于jdk安装路径bin目录)去排查,现在就给大家介绍一下:

1.jconsole

可以看出,线程1和线程2发生了死锁,死锁发生的位置一目了然

2.jvisualvm

可以看出,发生了死锁,线程1和线程2尝试获取的锁是对方的锁。

3.jmc

可以看出,同样检测出了死锁情况
无论是用哪个工具排查死锁情况都是OK的。

死锁发生的条件

1.互斥条件(一个锁只能被一个线程占有,当一个锁被一个线程持有之后,不能再被其他线程持有);
2.请求拥有(一个线程拥有一把锁之后,又去尝试请求拥有另外一把锁);可以解决
3.不可剥夺(一个锁被一个线程占有之后,如果该线程没有释放锁,其他线程不能强制获得该锁);
4.环路等待条件(多线程获取锁时形成了一个环形链)可以解决

怎么解决死锁问题?

环路等待条件相对于请求拥有更容易实现,那么通过破坏环路等待条件解决死锁问题
破坏环路等待条件示意图:

针对于上面死锁举例中代码,解决死锁,具体看下面代码:

/**
 * 线程死锁问题
 */
public class DeadLock {
    public static void main(String[] args) {
        //创建两个锁对象
        Object lock1 = new Object();
        Object lock2 = new Object();

        //创建子线程
        /*
        线程1:①先获得锁1 ②休眠1s,让线程2获得锁2 ③线程1尝试获取锁2 线程2同理
         */
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                //线程1业务逻辑
                synchronized(lock1){
                    System.out.println("线程1得到了锁子1");
                    try {
                        //休眠1s,让线程2先得到锁2
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程1尝试获取锁2...");
                    synchronized(lock2){
                        System.out.println("线程1获得了锁2!");
                    }
                }
            }
        },"线程1");

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                //线程2业务逻辑
                synchronized(lock2){
                    System.out.println("线程2得到了锁子2");
                    try {
                //休眠1s,让线程1先得到锁1;因为线程是并发执行我们不知道谁先执行
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程2尝试获取锁1...");
                    synchronized(lock1){
                        System.out.println("线程2获得了锁1");
                    }
                }
            }
        },"线程2");
        thread1.start();
        thread2.start();
    }
}

程序运行结果如下:

可以看出,通过破坏环路等待条件完美解决了死锁问题

线程通讯机制(wait/notify/notifyAll)

定义

线程通讯机制:一个线程的动作可以让另外一个线程感知到,这就是线程通讯机制。
wait():让当前线程进入休眠等待状态;
notify():唤醒当前对象上的休眠等待线程;
notifyAll():唤醒当前对象上的所有休眠等待线程。

相关面试重点

面试问题:
1.wait()使用时为什么需要加锁?
因为wait()必须在同步方法或者同步块中使用,也就是说wait()需要配合加锁一起使用(比如synchronized或Lock),调用对象调用wait()如果没有适当的锁,就会引发异常,因此说wait()使用时需要加锁。
2.wait()使用为什么要释放锁?
wait()是Objetc类中一个实例方法,默认是不传任何值的,不传值的时候表示让当前线程处于永久休眠等待状态,这样会造成一个锁被一个线程长时间一直拥有,为了避免这种问题的发生,使用wait()后必须释放锁。

wait()/notify()/notifyAll()使用时注意事项:
使用这三个方法时都必须进行加锁;
2.加锁的对象和调用wait()/notify()/notifyAll()对象必须是同一个对象;
3.一组wait()/notify()/notifyAll()必须是同一个对象;
4.notify()只能唤醒当前对象上的一个休眠等到线程;而notifyAll()可以唤醒当前对象上的所有休眠等待线程。

sleep(0)和wait(0)的区别:
1.sleep()是Thread类中一个静态方法,wait()是Object类中一个普通的成员方法;
2.sleep(0)会立即触发一次CPU的抢占执行,wait(0)会让当前线程无限休眠等待下去。

wait()和sleep()的区别:
相同点:
1.都会让当前线程进行休眠等待;
2.使用二者时都需处理InterruptedException异常(try/catch)。
不同点:
1.wait()是Object中普通成员方法,sleep是Thread中静态方法;
2.wait()使用可以不穿参数,sleep()必须传入一个大于等于0的参数;
3.wait()使用时必须配合加锁一起使用,sleep()使用时不需要加锁;
4.wait()使用时需要释放锁,如果sleep()加锁后不会释放锁;
5.wait()会让当前线程进入WAITING状态(默认没有明确的等待时间,当被别的线程唤醒或者wait()传参后超过等待时间量自己唤醒,将进入就绪状态),sleep()会让当前线程进入TIMED_WAITING状态(有明确的结束等待时间,但是这是死等的方式,休眠结束后进入就绪状态)。

*为什么wait()处于Object中而不是Thread中?(有点绕 我有点懵了…)
wait()的调用必须进行加锁和释放锁操作,而锁是属于对象级别非线程级别,也就是说锁针对于对象进行操作而不是线程;而线程和锁是一对多的关系,一个线程可以拥有多把锁,而一个线程只能被一个线程拥有,为了灵活操作,就将wait()放在Object中。

LockSupport

LockSupport是对wait()的升级,无需加锁也无需释放锁;

LockSupport.park()让线程休眠,和wait()一样会让线程进入WAITING状态;LockSupport.unpark()唤醒线程,可以唤醒对象上指定的休眠等待线程;(优势)

LockSupport与wait()区别

wait()与LockSupport的区别:

相同点:
1.二者都可以让线程进入休眠等待状态;
2.二者都可以传参或者不传参,让线程都会进入到WAITING状态。
不同点:
1.wait()需要配合加锁一起使用,LockSupport无需加锁;
2.wait()只能唤醒对象的随机休眠线程和全部线程,LockSupport可以唤醒对象的指定休眠线程。

到此这篇关于如何解决Java多线程死锁问题的文章就介绍到这了,更多相关Java多线程死锁内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java进阶必备之多线程编程

    一.图示 二.多线程编程 何为多线程,通俗的讲就是让你的代码同时干好几件事. 而我们的一个代码文件或者一个项目就是一个进程,而如果我们想提高效率,我们可以多开几个文件进行多进程,也可以在进程中创建多线程(多写几个方法),但是多进程比较耗费资源,所以一般推荐多线程,在代码里,让代码做几个文件做的事. 多线程编程可以让我们的代码拥有更高效率. 三.线程的工作过程 犹如上图 使用多线程需要先创建一个或者多个线程 然后让线程调用CPU资源,开始运行 然后运行完毕等待所有线程运行完毕 然后删除资源,结束线

  • Java多线程之Disruptor入门

    一.Disruptor简介 Disruptor目前是世界上最快的单机消息队列,由英国外汇交易公司LMAX开发,研发的初衷是解决内存队列的延迟问题(在性能测试中发现竟然与I/O操作处于同样的数量级).基于Disruptor开发的系统单线程能支撑每秒600万订单,2010年在QCon演讲后,获得了业界关注.2011年,企业应用软件专家Martin Fowler专门撰写长文介绍.同年它还获得了Oracle官方的Duke大奖.目前,包括Apache Storm.Camel.Log4j 2在内的很多知名项

  • JAVA多线程中join()方法的使用方法

    虽然关于讨论线程join()方法的博客已经非常极其特别多了,但是前几天我有一个困惑却没有能够得到详细解释,就是当系统中正在运行多个线程时,join()到底是暂停了哪些线程,大部分博客给的例子看起来都像是t.join()方法会使所有线程都暂停并等待t的执行完毕.当然,这也是因为我对多线程中的各种方法和同步的概念都理解的不是很透彻.通过看别人的分析和自己的实践之后终于想明白了,详细解释一下希望能帮助到和我有相同困惑的同学. 首先给出结论:t.join()方法只会使主线程(或者说调用t.join()的

  • Java多线程之synchronized关键字的使用

    一.使用在非静态方法上 public synchronized void syzDemo(){ System.out.println(System.currentTimeMillis()); System.out.println("进入synchronized锁:syzDemo"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } 二.使用在静态方法上 publi

  • java如何实现多线程的顺序执行

    场景 编写一个程序,启动三个线程,三个线程的name分别是A,B,C:,每个线程将自己的ID值在屏幕上打印5遍,打印顺序是ABCABC... 使用 synchronized 实现 public class MyService { private int flag = 1; public synchronized void printA(){ while (flag != 1) { try { this.wait(); } catch (InterruptedException e) { e.pr

  • Java多线程之ReentrantReadWriteLock源码解析

    一.介绍 1.1 ReentrantReadWriteLock ReentrantReadWriteLock 是一个读写锁,允许多个读或者一个写线程在执行. 内部的 Sync 继承自 AQS,这个 Sync 包含一个共享读锁 ReadLock 和一个独占写锁 WriteLock. 该锁可以设置公平和非公平,默认非公平. 一个持有写锁的线程可以获取读锁.如果该线程先持有写锁,再持有读锁并释放写锁,称为锁降级. WriteLock支持Condition并且与ReentrantLock语义一致,而Re

  • 总结java多线程之互斥与同步解决方案

    一.线程互斥与同步 互斥:指的是多个线程不能同时访问共享变量 同步:指的是多个线程按指定的顺序执行操作 在同时有多个线程运行过程中,如何达到互斥和同步呢? 加锁即可 在此使用黑马笔记中room例子来说明锁.(ps: 以前就了解锁,但总会记乱,发现使用形象化记忆后就很清楚) 解决互斥 锁就相当于上图的房子,里面放着会被并发访问的共享变量 此时绿色区域(owner)无线程,此时多个线程想并发访问房子里的共享变量,那么只允许其中一个线程进入房子访问,并把房门锁上. 剩下的没有拿到锁的线程只能在entr

  • Java多线程之线程同步

    volatile 先看个例子 class Test { // 定义一个全局变量 private boolean isRun = true; // 从主线程调用发起 public void process() { test(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } stop(); } // 启动一个子线程循环读取isRun private void test() {

  • Java多线程之线程的创建

    一.三种创建方式 基于什么创建 创建的方式 Thread类 继承Thread类 Runnable接口 实现Runnable接口 callable接口 实现callable接口 二.通过Thread类创建 2.1 步骤 自定义线程类继承Thread类 重写run()方法,编写线程执行体(当成main()方法用) 创建线程对象,调用start()方法启动线程 2.2 案例 创建两个线程,其中一个线程打印100以内的偶数,另一个线程打印100以内的奇数 //主方法 public class Demo0

  • java多线程创建及线程安全详解

    什么是线程 线程被称为轻量级进程,是程序执行的最小单位,它是指在程序执行过程中,能够执行代码的一个执行单位.每个程序程序都至少有一个线程,也即是程序本身. 线程的状态 新建(New):创建后尚未启动的线程处于这种状态 运行(Runable):Runable包括了操作系统线程状态的Running和Ready,也就是处于此状态的线程有可能正在执行,也有可能正在等待着CPU为它分配执行时间. 等待(Wating):处于这种状态的线程不会被分配CPU执行时间.等待状态又分为无限期等待和有限期等待,处于无

  • Java多线程之Interrupt中断线程详解

    一.测试代码 https://gitee.com/zture/spring-test/blob/master/multithreading/src/test/java/cn/diswares/blog/InterruptTests.java 二.测试 为了方便理解简介中 interrupt 的概念, 写个 DEMO 测试一下 /** * 调用 interrupt 并不会影响线程正常运行 */ @Test public void testInvokeInterrupt() throws Inter

  • Java基础之多线程的三种实现方式

    一.前言 Java多线程实现的三种方式有继承Thread类,实现Runnable接口,使用ExectorService.Callable.Future实现有返回结果的多线程.其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的. 二.继承Thread类实现多线程 1.Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start()实例方法. 2.start()方法是一个native方法,它将启动一个新线程

  • Java多线程之Park和Unpark原理

    一.基本使用 它们是 LockSupport 类中的方法 // 暂停当前线程 LockSupport.park(); // 恢复某个线程的运行 LockSupport.unpark(暂停线程对象) 应用:先 park 再 unpark Thread t1 = new Thread(() -> { log.debug("start..."); sleep(1); log.debug("park..."); LockSupport.park(); log.debu

  • Java多线程的具体介绍与使用笔记小结

    一.基本概念:线程.进程 1.1.进程与线程的具体介绍 线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径. 若一个进程同一时间并行执行多个线程,就是支持多线程的 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小 一个进程中的多个线程共享相同的内存单元/内存地址空间à它们从同一堆中分配对象,可以访问相同的变量和对象.这就使得线程间通信更简便.高效.但多个线程操作共享的系统资源可能就会带来安全的隐患. 进程(process),是程序的

  • Java实战之多线程模拟站点售票

    一.实验题目 二.分析 哦吼,这次的实验题目是一道非常经典的多线程买票问题.题目要求我们创建5个线程来模拟卖票,当然这其中就包含多线程存在也就是我们要解决的问题,重复卖票和超额卖票.即多个窗口卖出同一张票以及窗口卖出非正数编号的票. 不过这个问题可以先放一下,我们先来创建基础的线程模型,并在主方法中创建五个线程让他们跑起来: 话不多说,上代码. public class Ticket { public static void main(String[] args) { for(int i = 1

  • Java实现多线程中的静态代理模式

    前言 代理模式是一种设计模式,提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能. 简言之,代理模式就是设置一个中间代理来控制访问原目标对象,以达到增强原对象的功能和简化访问方式. 静态代理属于设计模式中的代理模式.反之则有动态代理,本篇文章不展开讲,有兴趣的可自行谷歌研究研究. 其实继承Thread也属于静态代理的一种,所以在这里学习静态代理有助于我们学习多线程. 静态代理 优点:可以在不修改目标对象的前提下扩

  • java多线程累加计数的实现方法

    题目 给定count=0:让5个线程并发累加到1000: 思路 创建一个类MyRunnable,实现Runnable(继承Thread类也可) 定义一个公共变量count(初始值为0),5个线程都可以访问到: 创建5个线程并发递增count到1000: 注意 这块注意Thread和Runnable类的区别,Thread类是线程类,可以直接new Thread().start运行.而Runnable类是任务类,需要一个线程来承载任务,通过new Thread(new Runnable()).sta

  • Java多线程之深入理解ReentrantLock

    前言 保证线程安全的方式有很多,比如CAS操作.synchronized.原子类.volatile保证可见性和ReentrantLock等,这篇文章我们主要探讨ReentrantLock的相关内容.本文基于JDK1.8讲述ReentrantLock. 一.可重入锁 所谓可重入锁,即一个线程已经获得了某个锁,当这个线程要再次获取这个锁时,依然可以获取成功,不会发生死锁的情况.synchronized就是一个可重入锁,除此之外,JDK提供的ReentrantLock也是一种可重入锁. 二.Reent

随机推荐