Java多线程死锁问题

Java多线程死锁问题死锁这么重要,请仔细阅读死锁问题死锁定义死锁举例如何排查死锁死锁发生的条件怎么解决死锁问题?线程通讯机制(wait/notify/notifyAll)LockSupport死锁问题死锁定义多线程编程中,因为抢占资源造成了线程无限等待的情况,此情况称为死锁。死锁举例注意:线程和锁的关系是:一个线程可以拥有多把锁,一个锁只能被一个线程拥有。当两个线程分别拥有一把各自的锁之后,又尝试去获取对方的锁,这样就会导致死锁情况的发生,具体先看下面代码:/***线程死锁问题*/public

大家好,又见面了,我是你们的朋友全栈君。

死锁问题

死锁定义

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

死锁举例

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

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

/** * 线程死锁问题 */
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 SolveDeadLock { 

public static void main(String[] args) { 

//创建两个锁对象
Object lock1 = new Object();
Object lock2 = new Object();
Thread thread1 = new Thread(new Runnable() { 

@Override
public void run() { 

//线程1业务逻辑
synchronized(lock1){ 

System.out.println("线程1得到了锁子1");
try { 

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(lock1){ 

System.out.println("线程2得到了锁子1");
try { 

Thread.sleep(1000);
} catch (InterruptedException e) { 

e.printStackTrace();
}
System.out.println("线程2尝试获取锁2...");
synchronized(lock2){ 

System.out.println("线程2获得了锁2");
}
}
}
},"线程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()使用时注意事项:
1.使用这三个方法时都必须进行加锁;
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状态(可以传参也可以不传,不传表示直接阻塞,传参表示经过这段时间后阻塞,必须让其他线程通过notify或notifyAll唤醒),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可以唤醒对象的指定休眠线程。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/158532.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • Intellij IDEA 主题导入与删除「建议收藏」

    一、IntelliJIDEA导入主题下载主题通过此[地址][1]下载自己喜欢的主题。在IntelliJIDEA中导入主题【File】-【ImportSettings】-选择下载的主题-重启IntelliJIDEA后生效。配置主题通过Ctrl+Alt+S快捷键开发Settings面板-【Editor】-【ColorSchem…

  • MPI编程简单介绍

    MPI编程简单介绍

  • java转大数据的学习路线

    java转大数据的学习路线首先这个文章是转载的,留着后面基础再扎实一点之后开始学习,感谢原文的作者,写出了如此清晰的学习路线。原文作者文章链接:https://blog.csdn.net/gitchat/article/details/78341484【不要错过文末彩蛋】申明:本文旨在为普通程序员(Java程序员最佳)提供一个入门级别的大数据技术学习路径,不适用于大数据工程师的进阶学习,也不适用于零编程基础的同…

  • 学习记录03(网页挂马)

    学习记录03(网页挂马)网页挂马将木马程序上传到网站,使用木马生成器生成一个网马,放到网页空间,在添加代码使木马在网页打开时运行常见的几种方式将木马伪装成页面元素,木马被浏览器自动加载到本地利用脚本运行的漏洞下载木马利用脚本运行的漏洞释放隐含在网页脚本中的木马将木马伪装成缺失的组件。或和缺失的组件绑在一起(flash播放插件等)通过脚本运行调用某些com组件,利用其漏洞下载木马在渲染页面内容的过程中…

  • 找回 Ubuntu 11.10 的传统界面 (Gnome Classic)

    找回 Ubuntu 11.10 的传统界面 (Gnome Classic)

  • windows定时关机命令 取消定时关机命令 查看DNS缓存命令 清除DNS缓存命令「建议收藏」

    windows定时关机命令 取消定时关机命令 查看DNS缓存命令 清除DNS缓存命令「建议收藏」shutdown-s-t以秒为单位的时间值注意这里的时间是以秒为单位哦例如:shutdown-s-t3600这是一个钟之后自动关机如果要取消自动关机,则输入:shutdown-a即可。另外再记个我觉得的常用命令吧ipconfig/displaydns这个显示是电脑上的dns缓存的全部信息ipconfig/flushdns这个是清除dns缓存的命令。…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号