如何解决Java多线程死锁问题
作者:Putarmor 发布时间:2022-08-11 15:51:02
死锁问题
死锁定义
多线程编程中,因为抢占资源造成了线程无限等待的情况,此情况称为死锁
。
死锁举例
注意:线程和锁的关系是:一个线程可以拥有多把锁,一个锁只能被一个线程拥有。
当两个线程分别拥有一把各自的锁之后,又尝试去获取对方的锁,这样就会导致死锁情况的发生,具体先看下面代码:
/**
* 线程死锁问题
*/
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可以唤醒对象的指定休眠线程。
来源:https://blog.csdn.net/weixin_44874269/article/details/116639326


猜你喜欢
- 打个比方:一个object就像一个大房子,大门永远打开。房子里有很多房间(也就是方法)。这些房间有上锁的(synchronized方法),
- C语言实现矩阵翻转 上下翻转与左右翻转实例代码:#include <stdio.h> void matrix (int m, i
- 最近做项目,ORM 使用的是 MyBatis,为了偷懒,我自然而然的想到了使用 MyBatis Generator(MBG)来生成数据库表对
- 本文实例为大家分享了java实现猜字母游戏的具体代码,供大家参考,具体内容如下案例需求:StepOne:系统随机生成一组随机的字符数组(不重
- 本文实例为大家分享了C#请求http向网页发送数据、网页接收,供大家参考,具体内容如下首先,我们需要的是什么东西?用POST方式请求http
- 最近项目中使用了mybatis-plus 3.1.1版本,发现使用lambda表达式方式的条件构造器,执行时会报错;但是我用单元测试却通过,
- springboot默认读取的配置文件名字是:“application.properties”和&a
- SpringBoot实战电商项目mall(30k+star)地址:https://github.com/macrozheng/mall摘要权
- 北京时间 2018 年 3 月 1 日早上,如约发布的 Spring Boot 2.0 在同步至 Maven 仓库时出现问题,导致在 Git
- 在java中用到的最多的时间类莫过于 java.util.Date了, 由于Date类中将getYear(),getMonth()等获取年、
- 本文实例为大家分享了Android悬浮窗菜单的具体代码,供大家参考,具体内容如下MainActivity.java代码:package si
- 缘起公司医疗业务人手比较少【小而美】的团队~ 较少采用的前端技术架构是:toC:小程序 toB2C: Flutter + H5(SPA -
- 本文演示如何在Android中实现ListView圆角效果。无论是网站,还是APP,人们都爱看一些新颖的视图效果。直角看多了,就想看看圆角,
- 前言:这段时间由于学校实行静态化管理,寝室门和校门都是用了人脸识别的装置,每次经过都会激发我的好奇心,也想自己搞一个人脸识别玩玩,随着开始查
- 本文实例讲述了C#递归实现回文判断算法,分享给大家供大家参考。具体实现方法如下:static void Main(string[] args
- 通过View提供的方法获取高度方式有两种:1, 当前显示的view中直接获取当前view高宽2, 通过Activity的getWindow(
- 本文为大家分享一个非常简单但又很常用的控件,跑马灯状态的TextView。当要显示的文本长度太长,又不想换行时用它来显示文本,一来可以完全的
- transport请求的发送和处理过程前一篇分析对nettytransport的启动及连接,本篇主要分析transport请求的发送和处理过
- 一、直接执行SQL查询:1、mappers文件节选<resultMap id="AcModelResultMap"
- 前言:各位小伙伴们,大家好,一日不见,如隔一日,今天我给大家分享一下大家在学习java过程当中遇到的一个问题,也是一道面试题,java中,O