原貼地址:http://www.gbunix.com/htmldata/2004_12/14/19/article_767_1.html
我突然發現還有很多東西需要我弄明白,比如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 start(){
??? if (!started) 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();
??? if(count1 != count2)
??????????? System.out.println("Unsynched");//一旦發現不同步,立即顯示
? }
}
class Watcher extends Thread {
? private Sharing2 p;
? public Watcher(Sharing2 p) {
??? this.p = p;
??? start();
? }
? public void run() {
??? while(true) {
????? p.s.synchTest();
????? try {
??????? sleep(500);
????? } catch (InterruptedException e){}
??? }
? }
}
public class Sharing2 {
? TwoCounter s;
? private static int accessCount = 0;
? public static void incrementAccess() {
??? accessCount++;
??? System.out.println("accessCount="+accessCount);
? }
? public static void main(String[] args) {
??? 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;i<5000;i++);
????? count2++;
????? ......
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="+count2);
????? try {
??????? sleep(500);
????? } catch (InterruptedException e){}
?? }
? }
? public synchronized void synchTest() {
??? Sharing2.incrementAccess();
??? if(count1 != count2)
??????????? System.out.println("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 syncObject's lock
}
在能進入同步塊之前,必須在synchObject上取得鎖。如果已有其他線程取得了這把鎖,塊便不能進入,必須等候那把鎖被釋放。
可從整個run()中刪除synchronized關鍵字,換成用一個同步塊包圍兩個關鍵行,從而完成對Sharing2例子的修改。但什么對象應作為鎖來使用呢?那個對象已由synchTest()標記出來了——也就是當前對象(this)!所以修改過的run()方法象下面這個樣子:
file://注意沒有synchronized關鍵字了
? public void run() {
??? while (true) {
????? synchronized(this){
??????? count1++;
??????? count2++;
????? }
????? System.out.println("Count1="+count1+",Count2="+count2);
????? try {
??????? sleep(500);
????? } catch (InterruptedException e){}
?? }
? }
file://注意,synchTest()還是要有synchronized關鍵字的,考慮一下為什么
這樣的話,synchTest方法就可以得到調用了,我們也可以看到accessCount的變化了.
posted on 2006-11-22 10:13
OMG 閱讀(229)
評論(0) 編輯 收藏 所屬分類:
Soket