菜鳥初學Java的備忘錄(七) (轉)
年1月22日 星期三 晴
我突然發現還有很多東西需要我弄明白,比如synchronized這個關鍵字的用法.因為在我昨天進行建立連線池套接字的研究的時候,發現假如我不弄清楚這個概念,根本就無法進行下去,所以我決定將自己對Socket的興趣先冷卻一下,而回過頭來看synchronized.
看了一上午的Think in ,覺得還是卓有成效的,應該立即寫下來加深印象.我感覺自己的大腦可重用性極低,總是需要生成新的記憶,從而耗費許多重複勞動.所以象記錄,分析,總結這樣類似的工作應該多多益善.
要弄清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) 止多次對一個執行緒Start方法
{
started=true;
super.start();
}
}
public void run() {
while (true) {
count1++;
果TwoCounter執行到這個時候,時間片被分配給了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 accesunt = 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方式執行或者以主視窗的方式執行開銷更大,使得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(sync) {
// 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()方法象下面這個樣子:
意沒有synchronized關鍵字了
public void run() {
while (true) {
synchronized(this){
count1++;
count2++;
}
System.out.println("Count1="+count1+",Count2="+count2);
try {
sleep(500);
} catch (InterruptedException e){}
}
}
意,synchTest()還是要有synchronized關鍵字的,考慮一下為什麼
這樣的話,synchTest方法就可以得到呼叫了,我們也可以看到accessCount的變化了.
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-993920/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 菜鳥初學Java的備忘錄(二) (轉)Java
- 菜鳥初學Java的備忘錄(五) (轉)Java
- 菜鳥初學Java的備忘錄(八) (轉)Java
- 菜鳥初學Java的備忘錄(九) (轉)Java
- 菜鳥初學Java的備忘錄(四) (轉)Java
- 菜鳥初學Java的備忘錄(六) (轉)Java
- 菜鳥初學Java的備忘錄(十) (轉)Java
- 菜鳥初學Java的備忘錄(一) (轉)Java
- 菜鳥初學Java的備忘錄(三) (轉)Java
- 初學Java的備忘錄Java
- 【備忘】Java菜鳥到大牛學習路線之實戰篇Java
- 【菜鳥學Java】2:初識J2EEJava
- Java備忘錄《集合》Java
- 菜鳥學Java(十七)——Jboss瘦身Java
- 菜鳥學Java(十八)——異常Java
- 菜鳥學習SQL注射(轉)SQL
- 菜鳥學Java(九)——Servlet的基本配置JavaServlet
- Java備忘錄《“==” 和 “equals”》Java
- Linux C++ 自學筆記之二<菜鳥初學系列> (轉)LinuxC++筆記
- 菜鳥學Java(十六)——Jboss簡介Java
- 菜鳥學Java(十一)——GET與POSTJava
- 【菜鳥學Java】11:Session技術JavaSession
- 【菜鳥學Java】10:Cookie技術JavaCookie
- 【菜鳥學Java】4:Servlet 技術JavaServlet
- 菜鳥學SSH(七)——Spring jar包詳解SpringJAR
- Dart 學習備忘錄Dart
- 菜鳥學Java(十四)——Java反射機制(一)Java反射
- 菜鳥學Java(十五)——Java反射機制(二)Java反射
- 菜鳥學堂之破解Sessioncookie的方法(轉)SessionCookie
- 菜鳥學Java(十)——分頁查詢Java
- IT菜鳥的學習生活
- 菜鳥學Java(七)——Ajax+Servlet實現無重新整理下拉聯動JavaServlet
- 【菜鳥學Java】7:JSP的基本語法JavaJS
- java菜鳥入門Java
- 菜鳥學資料庫(五)——MySQL必備命令資料庫MySql
- 備忘錄
- 【備忘錄】
- 菜鳥學Java(四)——JSP內建物件JavaJS物件