在上一篇文章的线程带来的风险中对死锁总结了一下,感觉太专业了,这次用白话总结一下;
死锁:哲学家吃饭问题,每个人都只拿到一只筷子,最终都饿死了。这个必要还是很恰当的;
当线程需要同时持有多个锁时,有可能产生死锁,一旦发生死锁线程就一直卡着,程序不再往下执行,我们只能通过终止并重启的方式来让程序重新执行。
挂起(suspend)、继续执行(resume)线程也不要用了,容易发生死锁
一、什么是死锁?
死锁是两个或更多线程阻塞着等待其它处于死锁状态的线程所持有的锁。死锁通常发生在多个线程同时但以不同的顺序请求同一组锁的时候。
例如,如果线程1锁住了A,然后尝试对B进行加锁,同时线程2已经锁住了B,接着尝试对A进行加锁,这时死锁就发生了。线程1永远得不到B,线程2也永远得不到A,并且它们永远也不会知道发生了这样的事情。为了得到彼此的对象(A和B),它们将永远阻塞下去。这种情况就是一个死锁。
二、造成死锁到原因?(接地气一点说有3点)
1.当前线程拥有其他线程需要到资源;
2.当前线程等待其他线程已拥有的资源;
3.都不放弃自己拥有的资源;
4.其他方式也可能死锁,如线程池死锁和网络连接死锁;
三、死锁的表现形式?
1.简单的顺序死锁

简单的顺序死锁
代码可以看出线程是交叉执行的,这样就会出现下面到情况:
线程A调用leftRight()方法,得到left锁
线程B调用rightLeft()方法,得到right锁
线程A和线程B都继续执行,此时线程A需要right锁才能继续往下执行。此时线程B需要left 锁才能继续往下执行。
但是:线程A的left锁并没有释放,线程B的right锁也没有释放。
所以他们都只能等待,而这种等待是无期限的-->永久等待-->死锁
2.动态锁顺序死锁

动态锁顺序死锁
看起来没问题,但是也有可能会发生死锁:
如果两个线程同时调用transferMoney()
线程A从X账户向Y账户转账
线程B从账户Y向账户X转账
那么就会发生死锁。

动态锁顺序死锁
3.协作对象之间发生死锁

协作对象之间发生死锁

协作对象之间发生死锁
在调用getImage()和setLocation(Point location)过程中都需要获取锁,但是没有释放锁。
四、死锁怎么样避免?
避免死锁从3个方面下手:
1.固定加锁的顺序(针对锁顺序死锁,加锁顺序)
上面动态锁顺序死锁的问题是加锁顺序不一致导致的
如果我们以固定的顺序来获取锁,那就解决了死锁到问题了

固定加锁的顺序解决办法
我们用hash值来固定加锁的顺序。
2.开放调用(针对对象之间协作造成的死锁)(我自己也不是很懂)
协作对象之间发生死锁的原因是调用某个方法时就需要持有锁,并且在方法内部也调用了其他带锁的方法!

开放调用

开放调用
我们采用调用某个方法时不需要持有锁,这种调用也被称为开放调用!
3.使用定时锁-->tryLock()(加锁时限)
使用显式Lock锁,在获取锁时使用tryLock()方法。当等待超过时限的时候,tryLock()不会一直等待,而是返回错误信息。
4.死锁检测
实在是发生了死锁,那我们只能用下面两个工具来检查了:
JconsoleJDK自带的图形化界面工具,使用JDK给我们的的工具JConsole
Jstack是JDK自带的命令行工具,主要用于线程Dump分析。
五、敲黑板划重点?
发生死锁的原因主要由于:
1.线程之间交错执行
解决:以固定的顺序加锁
2.执行某方法时就需要持有锁,且不释放
解决:缩减同步代码块范围,最好仅操作共享变量时才加锁
3.永久等待
解决:使用tryLock()定时锁,超过时限则返回错误信息
六、总结一下?
死锁对Java程序来说,是很复杂的,也很难发现问题。当两个线程被阻塞,每个线程在等待另一个线程时就发生死锁。
用synchronized关键字一定要注意死锁的问题,切记以下几句话:
对于同步,要时刻清醒在 哪个锁对象 上同步,这是关键。
对于同步代码块,要看清楚什么对象已经用于锁定(synchronized后面括号的内容)。在同一个对象上进行同步的线程将彼此阻塞,在不同对象上锁定的线程将永远不会彼此阻塞。
本文大部分来源网络和《Java并发编程实战》
欢迎指正留言