徹底明白Java的多線程-線程間的通信(二)

 二. 共享資源的同步

1. 同步的必要性
例4:

classSeq{ 
privatestaticintnumber = 0; 
privatestaticSeq seq = newSeq(); 
privateSeq() {} 
publicstaticSeq getInstance(){ 
returnseq; 

publicintget(){ 
number++;  //(a)
returnnumber; //(b)


publicclassTestThread{ 
publicstaticvoidmain(java/lang/String.java.html" target="_blank">
String
[] args){ 
Seq.getInstance().get(); //(1)
Seq.getInstance().get(); //(2)

}

上面是一個取得序列號的單例模式的例子,但調用get()時,可能會產生兩個相同的序列號:
當代碼(1)和(2)都試圖調用get()取得一個唯一的序列。當代碼(1)執行完代碼(a),正要執行代碼(b)時,它被中斷了並開始執行代碼(2)。一旦當代碼(2)執行完(a)而代碼(1)還未執行代碼(b),那麼代碼(1)和代碼(2)就將得到相同的值。
2. 通過synchronized實現資源同步
2.1 鎖標誌
2.1.1 每個對象都有一個標誌鎖。當對象的一個線程訪問了對象的某個synchronized數據(包括函數)時,這個對象就將被“上鎖”,所以被聲明爲synchronized的數據(包括函數)都不能被調用(因爲當前線程取走了對象的“鎖標誌”)。只有當前線程訪問完它要訪問的synchronized數據,釋放“鎖標誌”後,同一個對象的其它線程才能訪問synchronized數據。
2.1.2 每個class也有一個“鎖標誌”。對於synchronized static數據(包括函數)可以在整個class下進行鎖定,避免static數據的同時訪問。
例5:

classSeq{ 
privatestaticintnumber = 0; 
privatestaticSeq seq = newSeq(); 
privateSeq() {} 
publicstaticSeq getInstance(){ 
returnseq; 

publicsynchronizedintget(){ //(1)
number++; 
returnnumber; 

}

例5在例4的基礎上,把get()函數聲明爲synchronized,那麼在同一個對象中,就只能有一個線程調用get()函數,所以每個線程取得的number值就是唯一的了。
例6:

classSeq{ 
privatestaticintnumber = 0; 
privatestaticSeq seq = null; 
privateSeq() {} 
synchronizedpublicstaticSeq getInstance(){ //(1)
if(seq==null) seq = newSeq(); 
returnseq; 

publicsynchronizedintget(){ 
number++; 
returnnumber; 

}

例6把getInstance()函數聲明爲synchronized,那樣就保證通過getInstance()得到的是同一個seq對象。
2.2 non-static的synchronized數據只能在同一個對象的純種實現同步訪問,不同對象的線程仍可同時訪問。
例7:

classTestSynchronized implementsjava/lang/Runnable.java.html" target="_blank">
Runnable

publicsynchronizedvoidrun(){ //(1)
for(inti=0; i<10; i++){ 
java/lang/System.java.html" target="_blank">
System
.out.println(java/lang/Thread.java.html" target="_blank">
Thread
.currentThread().getName() + " : " + i); 
/*(2)*/
try{ 
java/lang/Thread.java.html" target="_blank">
Thread
.sleep(100); 

catch(java/lang/InterruptedException.java.html" target="_blank">
InterruptedException
e){ 
java/lang/System.java.html" target="_blank">
System
.out.println("Interrupted"); 




publicclassTestThread{ 
publicstaticvoidmain(java/lang/String.java.html" target="_blank">
String
[] args){ 
TestSynchronized r1 = newTestSynchronized(); 
TestSynchronized r2 = newTestSynchronized(); 
java/lang/Thread.java.html" target="_blank">
Thread
t1 = newjava/lang/Thread.java.html" target="_blank">
Thread
(r1, "t1"); 
java/lang/Thread.java.html" target="_blank">
Thread
t2 = newjava/lang/Thread.java.html" target="_blank">
Thread
(r2, "t2"); //(3)
//Thread t2 = new Thread(r1, "t2"); (4)
t1.start(); 
t2.start(); 

}

運行結果爲:
t1 : 0
t2 : 0
t1 : 1
t2 : 1
t1 : 2
t2 : 2
t1 : 3
t2 : 3
t1 : 4
t2 : 4
t1 : 5
t2 : 5
t1 : 6
t2 : 6
t1 : 7
t2 : 7
t1 : 8
t2 : 8
t1 : 9
t2 : 9
雖然我們在代碼(1)中把run()函數聲明爲synchronized,但由於t1、t2是兩個對象(r1、r2)的線程,而run()函數是non-static的synchronized數據,所以仍可被同時訪問(代碼(2)中的sleep()函數由於在暫停時不會釋放“標誌鎖”,因爲線程中的循環很難被中斷去執行另一個線程,所以代碼(2)只是爲了顯示結果)。
如果把例7中的代碼(3)註釋掉,並去年代碼(4)的註釋,運行結果將爲:
t1 : 0
t1 : 1
t1 : 2
t1 : 3
t1 : 4
t1 : 5
t1 : 6
t1 : 7
t1 : 8
t1 : 9
t2 : 0
t2 : 1
t2 : 2
t2 : 3
t2 : 4
t2 : 5
t2 : 6
t2 : 7
t2 : 8
t2 : 9
修改後的t1、t2是同一個對象(r1)的線程,所以只有當一個線程(t1或t2中的一個)執行run()函數,另一個線程才能執行。
2.3 對象的“鎖標誌”和class的“鎖標誌”是相互獨立的。
例8:

classTestSynchronized extendsjava/lang/Thread.java.html" target="_blank">
Thread

publicTestSynchronized(java/lang/String.java.html" target="_blank">
String
name){ 
super(name); 

publicsynchronizedstaticvoidprt(){ 
for(inti=10; i<20; i++){ 
java/lang/System.java.html" target="_blank">
System
.out.println(java/lang/Thread.java.html" target="_blank">
Thread
.currentThread().getName() + " : " + i); 
try{ 
java/lang/Thread.java.html" target="_blank">
Thread
.sleep(100); 

catch(java/lang/InterruptedException.java.html" target="_blank">
InterruptedException
e){ 
java/lang/System.java.html" target="_blank">
System
.out.println("Interrupted"); 



publicsynchronizedvoidrun(){ 
for(inti=0; i<10; i++){ 
java/lang/System.java.html" target="_blank">
System
.out.println(java/lang/Thread.java.html" target="_blank">
Thread
.currentThread().getName() + " : " + i); 
try{ 
java/lang/Thread.java.html" target="_blank">
Thread
.sleep(100); 

catch(java/lang/InterruptedException.java.html" target="_blank">
InterruptedException
e){ 
java/lang/System.java.html" target="_blank">
System
.out.println("Interrupted"); 




publicclassTestThread{ 
publicstaticvoidmain(java/lang/String.java.html" target="_blank">
String
[] args){ 
TestSynchronized t1 = newTestSynchronized("t1"); 
TestSynchronized t2 = newTestSynchronized("t2"); 
t1.start(); 
t1.prt(); //(1)
t2.prt(); //(2)

}

運行結果爲:
main : 10
t1 : 0
main : 11
t1 : 1
main : 12
t1 : 2
main : 13
t1 : 3
main : 14
t1 : 4
main : 15
t1 : 5
main : 16
t1 : 6
main : 17
t1 : 7
main : 18
t1 : 8
main : 19
t1 : 9
main : 10
main : 11
main : 12
main : 13
main : 14
main : 15
main : 16
main : 17
main : 18
main : 19

在代碼(1)中,雖然是通過對象t1來調用prt()函數的,但由於prt()是靜態的,所以調用它時不用經過任何對象,它所屬的線程爲main線程。
由於調用run()函數取走的是對象鎖,而調用prt()函數取走的是class鎖,所以同一個線程t1(由上面可知實際上是不同線程)調用run()函數且還沒完成run()函數時,它就能調用prt()函數。但prt()函數只能被一個線程調用,如代碼(1)和代碼(2),即使是兩個不同的對象也不能同時調用prt()。
3. 同步的優化
1) synchronized block
語法爲:synchronized(reference){ do this }
reference用來指定“以某個對象的鎖標誌”對“大括號內的代碼”實施同步控制。
例9:

classTestSynchronized implementsjava/lang/Runnable.java.html" target="_blank">
Runnable

staticintj = 0; 
publicsynchronizedvoidrun(){ 
for(inti=0; i<5; i++){ 
//(1)
java/lang/System.java.html" target="_blank">
System
.out.println(java/lang/Thread.java.html" target="_blank">
Thread
.currentThread().getName() + " : " + j++); 
try{ 
java/lang/Thread.java.html" target="_blank">
Thread
.sleep(100); 

catch(java/lang/InterruptedException.java.html" target="_blank">
InterruptedException
e){ 
java/lang/System.java.html" target="_blank">
System
.out.println("Interrupted"); 




publicclassTestThread{ 
publicstaticvoidmain(java/lang/String.java.html" target="_blank">
String
[] args){ 
TestSynchronized r1 = newTestSynchronized(); 
TestSynchronized r2 = newTestSynchronized(); 
java/lang/Thread.java.html" target="_blank">
Thread
t1 = newjava/lang/Thread.java.html" target="_blank">
Thread
(r1, "t1"); 
java/lang/Thread.java.html" target="_blank">
Thread
t2 = newjava/lang/Thread.java.html" target="_blank">
Thread
(r1, "t2"); 
t1.start(); 
t2.start(); 

}

運行結果爲:
t1 : 0
t1 : 1
t1 : 2
t1 : 3
t1 : 4
t2 : 5
t2 : 6
t2 : 7
t2 : 8
t2 : 9
上面的代碼的run()函數實現了同步,使每次打印出來的j總是不相同的。但實際上在整個run()函數中,我們只關心j的同步,而其餘代碼同步與否我們是不關心的,所以可以對它進行以下修改:

classTestSynchronized implementsjava/lang/Runnable.java.html" target="_blank">
Runnable

staticintj = 0; 
publicvoidrun(){ 
for(inti=0; i<5; i++){ 
//(1)
synchronized(this){ 
java/lang/System.java.html" target="_blank">
System
.out.println(java/lang/Thread.java.html" target="_blank">
Thread
.currentThread().getName() + " : " + j++); 

try{ 
java/lang/Thread.java.html" target="_blank">
Thread
.sleep(100); 

catch(java/lang/InterruptedException.java.html" target="_blank">
InterruptedException
e){ 
java/lang/System.java.html" target="_blank">
System
.out.println("Interrupted"); 




publicclassTestThread{ 
publicstaticvoidmain(java/lang/String.java.html" target="_blank">
String
[] args){ 
TestSynchronized r1 = newTestSynchronized(); 
TestSynchronized r2 = newTestSynchronized(); 
java/lang/Thread.java.html" target="_blank">
Thread
t1 = newjava/lang/Thread.java.html" target="_blank">
Thread
(r1, "t1"); 
java/lang/Thread.java.html" target="_blank">
Thread
t2 = newjava/lang/Thread.java.html" target="_blank">
Thread
(r1, "t2"); 
t1.start(); 
t2.start(); 

}

運行結果爲:
t1 : 0
t2 : 1
t1 : 2
t2 : 3
t1 : 4
t2 : 5
t1 : 6
t2 : 7
t1 : 8
t2 : 9
由於進行同步的範圍縮小了,所以程序的效率將提高。同時,代碼(1)指出,當對大括號內的println()語句進行同步控制時,會取走當前對象的“鎖標誌”,即對當前對象“上鎖”,不讓當前對象下的其它線程執行當前對象的其它synchronized數據。

發佈了8 篇原創文章 · 獲贊 1 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章