失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > 多线程的死锁问题

多线程的死锁问题

时间:2019-11-23 12:03:49

相关推荐

多线程的死锁问题

本文来说下多线程的死锁问题

文章目录

死锁讲解锁顺序死锁动态锁顺序死锁协作对象之间发生死锁避免死锁的方法固定锁顺序避免死锁开放调用避免死锁使用定时锁死锁检测本文小结

死锁讲解

在Java中使用多线程,就会有可能导致死锁问题。死锁会让程序一直卡住,不再程序往下执行。我们只能通过中止并重启的方式来让程序重新执行。

这是我们非常不愿意看到的一种现象,我们要尽可能避免死锁的情况发生!

造成死锁的原因可以概括成三句话:

当前线程拥有其他线程需要的资源当前线程等待其他线程已拥有的资源都不放弃自己拥有的资源

锁顺序死锁

首先我们来看一下最简单的死锁(锁顺序死锁)是怎么样发生的:

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锁也没有释放。所以他们都只能等待,而这种等待是无期限的–>永久等待–>死锁

动态锁顺序死锁

我们看一下下面的例子,你认为会发生死锁吗?

// 转账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);

协作对象之间发生死锁

我们来看一下下面的例子:

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()。如果等待获取锁时间超时,则抛出异常而不是一直等待!

固定锁顺序避免死锁

上面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值来固定加锁的顺序,这样我们就不会发生死锁的问题了!

开放调用避免死锁

在协作对象之间发生死锁的例子中,主要是因为在调用某个方法时就需要持有锁,并且在方法内部也调用了其他带锁的方法!

如果在调用某个方法时不需要持有锁,那么这种调用被称为开放调用!

我们可以这样来改造:

同步代码块最好仅被用于保护那些涉及共享状态的操作!

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) {}}}

使用开放调用是非常好的一种方式,应该尽量使用它。

使用定时锁

使用显式Lock锁,在获取锁时使用tryLock()方法。当等待超过时限的时候,tryLock()不会一直等待,而是返回错误信息。

使用tryLock()能够有效避免死锁问题~~

死锁检测

虽然造成死锁的原因是因为我们设计得不够好,但是可能写代码的时候不知道哪里发生了死锁。

JDK提供了两种方式来给我们检测:

JconsoleJDK自带的图形化界面工具,使用JDK给我们的的工具JConsoleJstack是JDK自带的命令行工具,主要用于线程Dump分析。

本文小结

本文详细介绍了多线程的死锁问题。

如果觉得《多线程的死锁问题》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。