所謂死鎖: 是指兩個或兩個以上的進程在執行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處于死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱為死鎖進程。
百度百科
我們使用加鎖機制來確保線程安全,但如果過度使用加鎖,則可能導致鎖順序死鎖(Lock-Ordering Deadlock).同樣,我們使用線程池和信號量來限制對資源的使用,但這些被限制的行為可能會導致資源死鎖(Resource DeadLock).
Java應用程序無法從死鎖中恢復過來,因此在程序設計時要排除那些可能導致死鎖出現的條件
下面對死鎖的一些簡單介紹
一,死鎖示例
1,鎖順序死鎖
- 線程以不同的順序來獲得相同的鎖,那么就可能出現死鎖
- 若所有線程以固定的順序來獲得鎖,那么在程序中就不會出現鎖順序死鎖的問題
示例:
public class Test {
public static void main(String[] args) {
LeftRightDeadLock deadLock = new LeftRightDeadLock();
LeftRightThread leftRightThread = new LeftRightThread(deadLock);
RightLeftThread rightLeftThread = new RightLeftThread(deadLock);
leftRightThread.start();
rightLeftThread.start();
}
}
class LeftRightThread extends Thread{
private LeftRightDeadLock deadLock;
public LeftRightThread(LeftRightDeadLock deadLock) {
this.deadLock = deadLock;
}
@Override
public void run() {
while(true){
deadLock.leftRight();
}
}
}
class RightLeftThread extends Thread{
private LeftRightDeadLock deadLock;
public RightLeftThread(LeftRightDeadLock deadLock) {
this.deadLock = deadLock;
}
@Override
public void run() {
while(true){
deadLock.rightLeft();
}
}
}
class LeftRightDeadLock{
private Object leftLock = new Object();
private Object rightLock = new Object();
public void leftRight(){
synchronized (leftLock) {
synchronized (rightLock) {
System.out.println(Thread.currentThread().getName()+" leftRight");
}
}
}
public void rightLeft(){
synchronized (rightLock) {
synchronized (leftLock) {
System.out.println(Thread.currentThread().getName()+" rightLeft");
}
}
}
通過讓線程獲取鎖的順序一致來避免死鎖
class LeftRightDeadLock{
private Object leftLock = new Object();
private Object rightLock = new Object();
public void leftRight(){
synchronized (leftLock) {
synchronized (rightLock) {
System.out.println(Thread.currentThread().getName()+" leftRight");
}
}
}
public void rightLeft(){
synchronized (leftLock) {
synchronized (rightLock) {
System.out.println(Thread.currentThread().getName()+" rightLeft");
}
}
}
2,在協作對象之間發生的死鎖
- 如果在持有鎖時調用某個外部方法,那么將出現活躍性問題.在這個外部方法中可能會獲取其他鎖(這可能會產生死鎖),或者阻塞時間過長,導致其他線程無法及時獲得當前被持有的鎖
- 解決辦法---開放調用
如果在調用某個方法時不需要持有鎖,那么這種調用被稱為開放調用(Open Call)
public class Test {
public static void main(String[] args) throws InterruptedException {
First first = null;
Second second = null;
first = new First();
second = new Second();
first.setSecond(second);
second.setFirst(first);
FirstThread firstThread = new FirstThread(first);
SecondThread secondThread = new SecondThread(second);
firstThread.start();
Thread.sleep(1000);
secondThread.start();
}
}
class FirstThread extends Thread{
First first;
FirstThread(First first){
this.first = first;
}
@Override
public void run() {
for(;;){
first.getFirst();
}
}
}
class SecondThread extends Thread{
Second second;
SecondThread(Second second){
this.second = second;
}
@Override
public void run() {
for(;;){
second.getSecond();
}
}
}
class First {
private Second second;
public synchronized String getFirst() {
second.setFirst(this);
System.out.println(Thread.currentThread());
return "first";
}
public synchronized void setSecond(Second second){
this.second = second;
}
}
class Second {
private First first;
public synchronized String getSecond() {
first.setSecond(this);
System.out.println(Thread.currentThread());
return "second";
}
public synchronized void setFirst(First first){
this.first = first;
}
}
解決后的代碼
class First {
private Second second;
public synchronized String getFirst() {
second.setFirst(this);
synchronized (this) {
System.out.println(Thread.currentThread());
}
return "first";
}
public synchronized void setSecond(Second second){
this.second = second;
}
}
class Second {
private First first;
public String getSecond() {
first.setSecond(this);
synchronized (this) {
System.out.println(Thread.currentThread());
}
return "second";
}
public synchronized void setFirst(First first){
this.first = first;
}
}
3,資源死鎖
當多個線程相互持有彼此正在等待的鎖而又不釋放自己已持有的鎖時會發生死鎖,當它們在相同的資源集合上等待時,也會發生死鎖.
二,死鎖的避免和診斷
1,死鎖的避免
使用Lock類定時的tryLock功能來代替內置鎖機制,可以檢測死鎖和從死鎖中回復過來.
當使用內置鎖時,只有沒有獲得鎖,就會永遠等待下去,而顯示鎖則可以指定一個超時時限,在等待超過該時間后tryLock會返回一個失敗信息.
2,通過線程轉儲信息來分析死鎖
JVM可以通過線程轉儲(Thread Dump)來幫助識別死鎖的發生.
線程轉儲包括各個運行中的線程的棧追蹤信息,這類似于發生異常時的棧追蹤信息.線程轉儲還包括加鎖信息,例如每個線程持有了那些鎖,在那些棧幀中獲得這些鎖,以及被阻塞的線程正在等待獲取哪一個鎖.
以第一個死鎖示例(鎖順序死鎖)
輸入命令: jstack -l 16601
其中16601是進程號
下面是部分的線程轉儲信息
........
........
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x00007fa04381beb8 (object 0x00000007aaadd8b0, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x00007fa04381bf68 (object 0x00000007aaadd8c0, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at LeftRightDeadLock.rightLeft(Test.java:54)
- waiting to lock <0x00000007aaadd8b0> (a java.lang.Object)
- locked <0x00000007aaadd8c0> (a java.lang.Object)
at RightLeftThread.run(Test.java:35)
"Thread-0":
at LeftRightDeadLock.leftRight(Test.java:46)
- waiting to lock <0x00000007aaadd8c0> (a java.lang.Object)
- locked <0x00000007aaadd8b0> (a java.lang.Object)
at LeftRightThread.run(Test.java:21)
Found 1 deadlock.
參考:
<<java編發編程實戰>>