因為馬上畢業了,最近一直在複習Java的基礎知識,多線程當然是重點了,今天上午一直在看線程的生命階段,其中有過時的方法suspend用來挂起一個線程。而關于該方法為何被抛棄了,看了開發文檔中是這麼描述的:【方法已經遭到反對,因為它具有固有的死鎖傾向。如果目标線程挂起時在保護關鍵系統資源的螢幕上保持有鎖,則在目标線程重新開始以前任何線程都不能通路該資源。如果重新開始目标線程的線程想在調用
resume
之前鎖定該螢幕,則會發生死鎖。】
看完上面的官方文檔,光看文字把頭都看暈了,感覺sleep和suspend對挂起線程的操作感覺又模糊了不少,尤其是對鎖的控制上面。
是以就模拟了兩個線程,代碼如下:
package a.b;
public class Test {
private static final int INDEX = 10;
public static void main(String[] args) {
try {
// 定義線程
Thread tchild = new Thread(new Runnable() {
public void run() {
try {
int a = 0;
for (long i = 0; i < 1000000; i++) {
while (a < INDEX)/** 如果在執行這句的時候調用了tchild.suspend(),那就不會導緻死鎖 */
a++;
}
System.out.println(i);
/**
* 如果正好在執行上面這句的時候調用了tchild.suspend(),
* 由于println()方法裡面保持有鎖,
* 是以,在本線程挂起的時候其他的線程就無法使用println方法中所持有的鎖,
* 這就導緻suspend方法容易導緻死鎖的原因。
*/
a = 0;/** 如果在執行這句的時候調用了tchid.suspend(),那就不會導緻死鎖 *//
}
} catch (Throwable e) {e.printStackTrace();}
}//run
});
tchild.start();/**開啟子線程*/
Thread.sleep(2000);/**讓主線程停止2s,子線程繼續運作2s*/
tchild.suspend();/**讓子線程挂起*/
/**讓子線程挂起的情況下再執行主線程中的列印操作*/
for (long i = 0; i < 1000000; i++) {
System.out.println("主線程"+i);
}
tchild.resume();//激活被挂起的線程 } catch (Throwable ex) { ex.printStackTrace(); } }
}
實驗結果是:
1)當常量INDEX=10的時候,輸出的結果大部分情況下是:運作到:
....
子線程152463
子線程152464
子線程152465
此時,列印了子線程的資訊過了兩秒後,子線程被挂起,但是主線程中的内容卻沒有繼續執行,這說明主線程已經被阻塞了;
原因是:子線程在運作到System.out.println(i);這一句的時候被suspend()方法挂起了,而由于該句調用的時候使用了鎖,
即out中的常量public final static PrintStream out = null;此處的out對象就是方法println中使用的鎖對象;是以在被suspend挂起後子線程任然持有鎖-out常量,是以當主線程運作for循環列印資訊的時候System.out.println("主線程"+i);,主線程根本拿不到鎖,是以造成線程死鎖;
2)當常量INDEX=100000000的時候,輸出結果大部分情況下是,運作到:
.....
主線程999998
主線程999999
子線程2605
子線程2606
...
子線程999998
子線程999999
,同樣的道理因為子線程并不是在運作System.out.println(i);這一句的時候被suspend()方法挂起的,是以就不會持有列印語句中的鎖-out常量,是以不會産生死鎖現象。
綜合上面的分析:
調用suspend方法來挂起一個線程A的時候,如果這好在執行一端具有同步鎖的代碼塊的時候被挂起,那這個同步鎖是不會被釋放的,那麼當線程B執行時,如果内部代碼需要使用到該鎖,而此時的鎖是被線程B中代碼塊持有的,此時就會導緻線程死鎖。這就是為什麼suspend不安全的地方,因為它無法控制線程内部代碼塊持有的鎖的釋放。
關于sleep調用後不會釋放鎖是指的,當線程A的同步代碼塊中調用了sleep的使用,該代碼塊持有的鎖不會被釋放,那麼線程B,C,..去執行具有相同鎖對象的代碼塊的時候就會被阻塞。