多线程之死锁就是这么简单
前言
只有光头才能变强
回顾前面:
- ThreadLocal就是这么简单
- 多线程三分钟就可以入个门了!
- 多线程基础必要知识点!看了学习多线程事半功倍
- Java锁机制了解一下
- AQS简简单单过一遍
- Lock锁子类了解一下
- 线程池你真不来了解一下吗?
本篇主要是讲解死锁,这是我在多线程的最后一篇了。主要将多线程的基础过一遍,以后有机会再继续深入!
死锁是在多线程中也是比较重要的知识点了!
那么接下来就开始吧,如果文章有错误的地方请大家多多包涵,不吝在评论区指正哦~
声明:本文使用JDK1.8
一、死锁讲解
在Java中使用多线程,就会有可能导致死锁问题。死锁会让程序一直卡住,不再程序往下执行。我们只能通过中止并重启的方式来让程序重新执行。
- 这是我们非常不愿意看到的一种现象,我们要尽可能避免死锁的情况发生!
造成死锁的原因可以概括成三句话:
- 当前线程拥有其他线程需要的资源
- 当前线程等待其他线程已拥有的资源
- 都不放弃自己拥有的资源
1.1锁顺序死锁
首先我们来看一下最简单的死锁(锁顺序死锁)是怎么样发生的:
public class LeftRightDeadlock {private final Object left = new Object();private final Object right = new Object();public void leftRight() {// 得到left锁synchronized (left) {// 得到right锁synchronized (right) {doSomething();}}}public void rightLeft() {// 得到right锁synchronized (right) {// 得到left锁synchronized (left) {doSomethingElse();}}}
}
复制代码
我们的线程是交错执行的,那么就很有可能出现以下的情况:
- 线程A调用
leftRight()
方法,得到left锁 - 同时线程B调用
rightLeft()
方法,得到right锁 - 线程A和线程B都继续执行,此时线程A需要right锁才能继续往下执行。此时线程B需要left锁才能继续往下执行。
- 但是:线程A的left锁并没有释放,线程B的right锁也没有释放。
- 所以他们都只能等待,而这种等待是无期限的-->永久等待-->死锁
1.2动态锁顺序死锁
我们看一下下面的例子,你认为会发生死锁吗?
// 转账public static void transferMoney(Account fromAccount,Account toAccount,DollarAmount amount)throws InsufficientFundsException {// 锁定汇账账户synchronized (fromAccount) {// 锁定来账账户synchronized (toAccount) {// 判余额是否大于0if (fromAccount.getBalance().compareTo(amount) < 0) {throw new InsufficientFundsException();} else {// 汇账账户减钱fromAccount.debit(amount);// 来账账户增钱toAccount.credit(amount);}}}}
复制代码
上面的代码看起来是没有问题的:锁定两个账户来判断余额是否充足才进行转账!
但是,同样有可能会发生死锁:
- 如果两个线程同时调用
transferMoney()
- 线程A从X账户向Y账户转账
- 线程B从账户Y向账户X转账
- 那么就会发生死锁。
A:transferMoney(myAccount,yourAccount,10);B:transferMoney(yourAccount,myAccount,20);复制代码
1.3协作对象之间发生死锁
我们来看一下下面的例子:
public class CooperatingDeadlock {// Warning: deadlock-prone!class Taxi {@GuardedBy("this") private Point location, destination;private final Dispatcher dispatcher;public Taxi(Dispatcher dispatcher) {this.dispatcher = dispatcher;}public synchronized Point getLocation() {return location;}// setLocation 需要Taxi内置锁public synchronized void setLocation(Point location) {this.location = location;if (location.equals(destination))// 调用notifyAvailable()需要Dispatcher内置锁dispatcher.notifyAvailable(this);}public synchronized Point getDestination() {return destination;}public synchronized void setDestination(Point destination) {this.destination = destination;}}class Dispatcher {@GuardedBy("this") private final Set<Taxi> taxis;@GuardedBy("this") private final Set<Taxi> availableTaxis;public Dispatcher() {taxis = new HashSet<Taxi>();availableTaxis = new HashSet<Taxi>();}public synchronized void notifyAvailable(Taxi taxi) {availableTaxis.add(taxi);}// 调用getImage()需要Dispatcher内置锁public synchronized Image getImage() {Image image = new Image();for (Taxi t : taxis)// 调用getLocation()需要Taxi内置锁image.drawMarker(t.getLocation());return image;}}class Image {public void drawMarker(Point p) {}}
}
复制代码
上面的getImage()
和setLocation(Point location)
都需要获取两个锁的
- 并且在操作途中是没有释放锁的
这就是隐式获取两个锁(对象之间协作)..
这种方式也很容易就造成死锁.....
二、避免死锁的方法
避免死锁可以概括成三种方法:
- 固定加锁的顺序(针对锁顺序死锁)
- 开放调用(针对对象之间协作造成的死锁)
- 使用定时锁-->
tryLock()
- 如果等待获取锁时间超时,则抛出异常而不是一直等待!
2.1固定锁顺序避免死锁
上面transferMoney()
发生死锁的原因是因为加锁顺序不一致而出现的~
- 正如书上所说的:如果所有线程以固定的顺序来获得锁,那么程序中就不会出现锁顺序死锁问题!
那么上面的例子我们就可以改造成这样子:
public class InduceLockOrder {// 额外的锁、避免两个对象hash值相等的情况(即使很少)private static final Object tieLock = new Object();public void transferMoney(final Account fromAcct,final Account toAcct,final DollarAmount amount)throws InsufficientFundsException {class Helper {public void transfer() throws InsufficientFundsException {if (fromAcct.getBalance().compareTo(amount) < 0)throw new InsufficientFundsException();else {fromAcct.debit(amount);toAcct.credit(amount);}}}// 得到锁的hash值int fromHash = System.identityHashCode(fromAcct);int toHash = System.identityHashCode(toAcct);// 根据hash值来上锁if (fromHash < toHash) {synchronized (fromAcct) {synchronized (toAcct) {new Helper().transfer();}}} else if (fromHash > toHash) {// 根据hash值来上锁synchronized (toAcct) {synchronized (fromAcct) {new Helper().transfer();}}} else {// 额外的锁、避免两个对象hash值相等的情况(即使很少)synchronized (tieLock) {synchronized (fromAcct) {synchronized (toAcct) {new Helper().transfer();}}}}}
}
复制代码
得到对应的hash值来固定加锁的顺序,这样我们就不会发生死锁的问题了!
2.2开放调用避免死锁
在协作对象之间发生死锁的例子中,主要是因为在调用某个方法时就需要持有锁,并且在方法内部也调用了其他带锁的方法!
- 如果在调用某个方法时不需要持有锁,那么这种调用被称为开放调用!
我们可以这样来改造:
- 同步代码块最好仅被用于保护那些涉及共享状态的操作!
class CooperatingNoDeadlock {@ThreadSafeclass Taxi {@GuardedBy("this") private Point location, destination;private final Dispatcher dispatcher;public Taxi(Dispatcher dispatcher) {this.dispatcher = dispatcher;}public synchronized Point getLocation() {return location;}public synchronized void setLocation(Point location) {boolean reachedDestination;// 加Taxi内置锁synchronized (this) {this.location = location;reachedDestination = location.equals(destination);}// 执行同步代码块后完毕,释放锁if (reachedDestination)// 加Dispatcher内置锁dispatcher.notifyAvailable(this);}public synchronized Point getDestination() {return destination;}public synchronized void setDestination(Point destination) {this.destination = destination;}}@ThreadSafeclass Dispatcher {@GuardedBy("this") private final Set<Taxi> taxis;@GuardedBy("this") private final Set<Taxi> availableTaxis;public Dispatcher() {taxis = new HashSet<Taxi>();availableTaxis = new HashSet<Taxi>();}public synchronized void notifyAvailable(Taxi taxi) {availableTaxis.add(taxi);}public Image getImage() {Set<Taxi> copy;// Dispatcher内置锁synchronized (this) {copy = new HashSet<Taxi>(taxis);}// 执行同步代码块后完毕,释放锁Image image = new Image();for (Taxi t : copy)// 加Taix内置锁image.drawMarker(t.getLocation());return image;}}class Image {public void drawMarker(Point p) {}}}复制代码
使用开放调用是非常好的一种方式,应该尽量使用它~
2.3使用定时锁
使用显式Lock锁,在获取锁时使用tryLock()
方法。当等待超过时限的时候,tryLock()
不会一直等待,而是返回错误信息。
使用tryLock()
能够有效避免死锁问题~~
2.4死锁检测
虽然造成死锁的原因是因为我们设计得不够好,但是可能写代码的时候不知道哪里发生了死锁。
JDK提供了两种方式来给我们检测:
- JconsoleJDK自带的图形化界面工具,使用JDK给我们的的工具JConsole
- Jstack是JDK自带的命令行工具,主要用于线程Dump分析。
具体可参考:
- www.cnblogs.com/flyingeagle…
三、总结
发生死锁的原因主要由于:
- 线程之间交错执行
- 解决:以固定的顺序加锁
- 执行某方法时就需要持有锁,且不释放
- 解决:缩减同步代码块范围,最好仅操作共享变量时才加锁
- 永久等待
- 解决:使用
tryLock()
定时锁,超过时限则返回错误信息
- 解决:使用
在操作系统层面上看待死锁问题(这是我之前做的笔记、很浅显):
- 操作系统第五篇【死锁】
参考资料:
- 《Java核心技术卷一》
- 《Java并发编程实战》
- 《计算机操作系统 汤小丹》
如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章,想要获取更多的Java资源的同学,可以关注微信公众号:Java3y。
文章的目录导航:
- zhongfucheng.bitcron.com/post/shou-j…
多线程之死锁就是这么简单相关推荐
- 线程死锁问题以及简单案例分析
线程死锁问题以及简单案例分析 1. 什么是死锁? 1.1 概述 1.2. 业务场景--举例说明 2. 死锁例子 2.1 代码 2.2 效果 2.3 代码分析 3. 解决案例中死锁问题 4. 怎么避免线 ...
- python多线程实现生产者消费者_用Python实现多线程“生产者-消费者”模型的简单例子...
用 Python 实现多线程"生产者 - 消费者"模型的简单例子 生产者消费者问题是一个著名的线程同步问题, 该问题描述如下: 有一个生产者在生产产品, 这些产品将提供给若干个消费 ...
- Java多线程之死锁编码及定位分析
Java多线程之死锁编码及定位分析 目录 死锁是什么 代码实现 死锁解决办法 1. 死锁是什么 死锁是指两个或两个以上的进程在执行过程中因争夺资而造成的一种互相等待的现象,若无外力干涉那它们都将无法推 ...
- JAVA的多线程、死锁、线程间通信、如何规避死锁、线程安全的单例模式
主要内容: 多线程 线程和进程间的关系 Java中的线程理论 Java中线程类的实现方式 Java中线程的常用方法 线程安全性问题 线程间通信 线程的死锁 如何规避死锁 线程安全的单例模式 多线程 线 ...
- 多线程产生死锁的四个必要条件
多线程产生死锁的四个必要条件 1.互斥条件:任意时刻一个资源只能给一个进程使用,其他进程若申请一个资源,而该资源被另一进程占有时,则申请 者等待直到资源被占有者释放. 2.不可剥夺条件:进程所获得的资 ...
- 锁用不好,可能把自己锁住哦!(解决多线程的死锁问题)
多线程de小事情 导航不迷路: 程序.进程以及线程的爱恨情仇 最简单实现多线程的方法(Thread) 简单易懂的多线程(通过实现Runnable接口实现多线程) 常用获取线程基本信息的方法(新手专属) ...
- Python 多线程中死锁了怎么办?
一.死锁 在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁. 就好像在现实社会中,男女双方在闹别扭之后,都在等待对方先道歉. 如果双方都这样固执地等待对方 ...
- python—多线程之死锁
一.造成死锁的原因 1.在多线程程序中,死锁问题很大一部分是由于线程同时获取多个锁造成的. 2.在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁. 3.尽 ...
- scratch做简单跑酷游戏_腾讯游戏学院专家:做一个多线程游戏框架可以多简单?...
导语 如何做一个多线程游戏框架?腾讯游戏学院专家Tao将在本文通过一个demo来说说游戏逻辑的多线程化. 众所周知现在各种游戏终端的发展十分迅猛.其中一个共同的特征是"多核化",由 ...
最新文章
- 衡阳技师学校计算机系,2017级计科师范专业到衡阳技师学院见习
- 从Handler.post(Runnable r)再一次梳理Android的消息机制(以及handler的内存泄露)
- 清空mysql注册表步骤_完全卸载MySQL 数据库清空MySql注册表
- ST表(模板)「 查询区间最值 」
- 免费python基础笔记_python基础笔记(一)
- Java ByteArrayInputStream mark()方法与示例
- SOCKET聊天室字符界面版
- Linux文件、文件内容搜索大全(find,grep命令)
- OSChina 周三乱弹 —— 究竟是谁走漏风声
- freemark 应用以及优势
- (3)机器学习_逻辑模型_决策树
- 实对称矩阵的特征值求法_“绝境之下”,如何求解矩阵的特征值?
- excel快捷键大全常用分享
- Linux内核kernel panic机制浅析
- linux 终端 reboot,讲解Linux基础命令之:man、reboot
- PostgreSQL SRF函数上索引实现方法
- mocha java mv_代码覆盖Mocha
- Eclipse插件开发JDT组件介绍
- Qt创建设计师自定义控件(Qt Designer自定义控件)
- 大学必考计算机软件cad,大学CAD考试试题单选多选作图操作题.doc
热门文章
- python国内谁的书最好看_强烈建议|转行Python最好看一下这篇文章
- modbus报文解析实例_云原生、全栈可编程的下一代SDN解析与实践 (一)丨传统SDN架构演进...
- 如何编写优雅的代码:07. 设计模式应用案例(下)
- SQL:REGEXP
- 日语编程语言抚子 - 第三版特色初探
- (原创)浅谈任意文件下载漏洞的利用
- static成员函数和static成员
- 第八十四节,css布局小技巧及font-awesome图标使用
- java Object类是可以接收集合类型的
- WebForm连接数据库实例