首頁  >  文章  >  Java  >  菜鳥初學Java的備忘錄(七)

菜鳥初學Java的備忘錄(七)

黄舟
黄舟原創
2016-12-20 13:52:481306瀏覽


我忽然發現還有很多東西需要我弄明白,比如synchronized這個要害字的用法.因為在我昨天進行創建連接池套接字的研究的時候,發現假如我不弄清楚這個概念,根本就無法進行下去,所以我決定將自己對Socket的愛好先冷卻一下,而回過頭來看synchronized.

看了一上午的Think in java,覺得還是卓有成效的,應該立即寫下來加深印象.我感覺自己的大腦可重用性極低,總是需要生成新的記憶對象,從而耗費許多重複勞動.所以像記錄,分析,總結這樣類似的工作應該多多益善.

要弄清synchronized的用法,首先要知道它是用來解決什麼問題的.既然synchronized是同步的意思,那麼它當然就是來解決不同步的問題的.下面就舉一個不同步的例子來演示可能出現的問題.

在這個例子當中,我們會建立兩個執行緒類別.一個叫TwoCounter,其工作是對兩個計數器變數同時進行累加,從1開始,你馬上會想道,我們是要用它來實現一個同步.另一個物件叫Watcher,顧名思義,是用來做監視工作的,它負責檢查TwoCounter線程中的兩個計數器的值是否相等,看起來這似乎是毫無意義的工作,因為既然是同步累加的,那麼兩個計數器的值怎麼可能不相等呢??

但,事實情況不是這樣的.我們先來看程序.在看這個程序之前,最好先翻翻Think in Java的14.2.1,我的程序實際上是根據該節中給出的例子簡化的,其中的主類改作了Sharing2

class TwoCounter extends Thread {
PRivate int count1 = 0, count2 = 0;
private boolean started=false;
public void ) file://防止多次對一個執行緒呼叫Start方法
{
started=true;
super.start();
}
}
public void run() {
while (true) {
count1++;
file ://假如TwoCounter運行到這個時候,CPU時間片被分配給了Watcher,那麼這個時候Watcher讀出來的兩個計數器的值當然會不一樣了,這個可能性是存在的。 「這是由線程的本質造成的??它們可在任何時候掛起(暫停)。所以在上述兩行的執行時刻之間,有時會出現執行暫停現象。同時,Watcher線程也正好跟隨進來,並且剛好在這個時候比較,造成計數器出現不相等的情況。」(Think in Java)
count2++;
System.out.println("Count1="+count1+",Count2="+count2);
try {
sleep(500);
} catch (InterruptedException e){}
}
}

public void synchTest() {
Sharing2.incrementaccess();; Unsynched");//一旦發現不同步,立即顯示
}
}

class Watcher extends Thread {
private Sharing2 p;
public Watcher(Sharing2 p) { 
this.p = p;
public Watcher(Sharing2 p) { 
this.p = p;
start();
start); }
p.s.synchTest();
try {
sleep(500);
} catch (InterruptedException e){}
}
}
} catch (InterruptedException e){}
}
}
}
}
TwoCounter s;
private static int accessCount = 0;
public static void incrementAccess() {
accessCount++;
System.out.println("accessCount="+accessCount); ) {
Sharing2 aaa = new Sharing2();
aaa.s=new TwoCounter();
aaa.s.start();//開啟TwoCounter執行緒
new Watcher(aaa);//開啟Watcher執行緒
}


上面的註釋講得很清楚了,有可能出現不同步的情況.但希奇的是,我在運行的時候,卻始終沒有碰到不同步的情況,那麼只有一種情況,就是程序中count1++和count2++幾乎是同時進行的,watcher線程插不進來,但是為什麼Think in Java上面的程式運行之後就肯定有不同步的情況呢?兩個程序的原理是完全一樣的,唯一不同的是我的程式較為簡單,並且在命令列下運行,未使用GUI.難道是因為使用Applet方式運行或以Windows主視窗的方式運行開銷更大,使得watcher有機可趁嗎?於是我試著在count1++和count2++之間加了一條循環語句,人為的增大空隙,目的是為了讓watcher好插進來,造成監測出來的count1不等於count2的情況,實現不同步.修改後的程序是這樣的
.... ..
count1++;
for(int i=0;icount2++;
......

OK!再運行程序,很快就有不同步現象產生了,這似乎證實我剛才的分析是正確的.但希奇的是,輸出了一次Unsynchrized之後,以後就再也沒有出現了,也就是說,watcher線程只有一次檢測到了兩個計數器count不同.這讓我覺得有點鬱悶,是巧合還是必然呢?也許是時間太短了,等下去肯定還會有Unsynchrized輸出的.

算了,這個問題先放下來,我們繼續.
既然出現了不同步的問題,那很顯然,解決的方法就是synchronized:將TwoCounter的run方法和SynchTest方法都變成同步方法.這樣做代表什麼意思呢? 有什麼好處呢?請參考Think in Java的14.2.2節,裡面有非常詳盡透徹的闡述.非凡是對監視器,也就是我們通常所說的對象鎖的概念,書中講的很清楚.

總之,需要修改的代碼如下:
class TwoCounter extends Thread {
public synchronized void run() {
while (true) {
count1++;
count2++;
System.out.println("Count1="+count1+",Count2="
System.out.println("Count1="+count1+",Count2="++count2); sleep(500);
} catch (InterruptedException e){}
}
}

public synchronized void synchTest() {
Sharing2.incrementAccess();
if( Unsynched");//一旦發現不同步,立即顯示
}
}

略去其它不寫,表示從問題到解決其實很簡單,呵呵.
我們注重到無論run()還是synchTest()都是「同步的」。假如只同步其中的一個方法,那麼另一個就可以自由忽略物件的鎖定,並可無礙地呼叫。所以必須記住一個重要的規則:對於存取某個要害共享資源的所有方法,都必須把它們設為synchronized,否則就不能正常地工作。

現在又碰到了一個新問題。 Watcher2永遠無法看到正在進行的事情,因為整個run()方法已設為「同步」。而且由於肯定要為每個物件運行run(),所以鎖永遠不能打開,而synchTest()永遠不會被呼叫。之所以能看到這個結果,是因為accessCount根本沒有改變。

為解決這個問題,我們能採取的一個方法是只將run()中的一部分程式碼隔離出來。想用這個辦法隔離出來的那部分程式碼叫作“要害區域”,而且要用不同的方式來使用synchronized要害字,以設定一個要害區域。 Java透過「同步區塊」提供對要害區域的支援;這次,我們用synchronized要害字指出物件的鎖定用於對其中封閉的程式碼進行同步。如下圖所示:

synchronized(syncObject) {
// This code can be accessed by only
// one thread at a time, assuming all
// threads respect thread at a time, assuming all
// threads respect thread at a time, assuming all
/// threads respect syncal′之前,必須在synchObject上取得鎖。假如已有其他線程取得了這把鎖,塊便不能進入,必須等候那把鎖被釋放。
可從整個run()中刪除synchronized要害字,換成用一個同步區塊包圍兩個要害行,從而完成對Sharing2例子的修改。但什麼物件要當作鎖來使用呢?那個物件已被synchTest()標記出來了??也就是當前物件(this)!所以修改過的run()方法像下面這個樣子:

file://注重沒有synchronized要害字了
public void run() {
while (true) {
synchronized(this){
count1++;
count++;
}
System.out.println("Count1="+count1+",Count2="+count2);
try {
sleep(500);
} catch (InterruptedException e){}
}
}
}
file //注重,synchTest()還是要有synchronized要害字的,考慮一下為什麼

這樣的話,synchTest方法就可以得到調用了,我們也可以看到accessCount的變化了. 

 以上就是菜鳥初學Java的備忘錄(七)的內容,更多相關內容請關注PHP中文網(www.php.cn)! 

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn