只有光头才能变强
回顾前面:html
本篇主要是讲解死锁,这是我在多线程的最后一篇了。主要将多线程的基础过一遍,之后有机会再继续深刻!java
死锁是在多线程中也是比较重要的知识点了!编程
那么接下来就开始吧,若是文章有错误的地方请你们多多包涵,不吝在评论区指正哦~c#
声明:本文使用JDK1.8
在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(); } } } }
咱们的线程是交错执行的,那么就颇有可能出现如下的状况:ide
leftRight()
方法,获得left锁rightLeft()
方法,获得right锁咱们看一下下面的例子,你认为会发生死锁吗?工具
// 转帐 public static void transferMoney(Account fromAccount, Account toAccount, DollarAmount amount) throws InsufficientFundsException { // 锁定汇帐帐户 synchronized (fromAccount) { // 锁定来帐帐户 synchronized (toAccount) { // 判余额是否大于0 if (fromAccount.getBalance().compareTo(amount) < 0) { throw new InsufficientFundsException(); } else { // 汇帐帐户减钱 fromAccount.debit(amount); // 来帐帐户增钱 toAccount.credit(amount); } } } }
上面的代码看起来是没有问题的:锁定两个帐户来判断余额是否充足才进行转帐!oop
可是,一样有可能会发生死锁:
transferMoney()
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 { @ThreadSafe 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; } 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; } } @ThreadSafe 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); } 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提供了两种方式来给咱们检测:
具体可参考:
发生死锁的缘由主要因为:
线程之间交错执行
执行某方法时就须要持有锁,且不释放
永久等待
tryLock()
定时锁,超过期限则返回错误信息在操做系统层面上看待死锁问题(这是我以前作的笔记、很浅显):
参考资料:
若是文章有错的地方欢迎指正,你们互相交流。习惯在微信看技术文章,想要获取更多的Java资源的同窗,能够 关注微信公众号:Java3y。为了你们方便,刚新建了一下 qq群:742919422,你们也能够去交流交流。谢谢支持了!但愿能多介绍给其余有须要的朋友
文章的目录导航: