菜鳥初學Java的備忘錄(八) (轉)

worldblog發表於2007-12-15
菜鳥初學Java的備忘錄(八) (轉)[@more@]

年1月24日 星期五 晴
我在22號的筆記中不是有一個疑問嗎?為什麼我編的沒有不同步的現象產生呢,我把它發到csdn上去了,現在我已經基本解決這個問題了,下面是論壇的回覆紀錄摘要

回覆人:bluesmile979(笑著) ( ) 信譽:100  2003-01-22 21:08:00  得分:0
說說我的看法,我認為最大的問題在於多執行緒,看了你的程式碼,好像只有兩個線成了。而例子中應該是比較多的執行緒,多個執行緒競爭時間片,被打斷的機率自然就大得多了。就算你加了迴圈,由於機器的運算速度,仍然沒有多個執行緒競爭那麼現象明顯。不知道各位以為如何。

回覆人:xm4014(forrest) ( ) 信譽:100  2003-01-22 22:07:00  得分:0
to bluesmile979(笑著)
我也曾經想到過是否因為執行緒太少而導致,但是我將Think in 的例程中的兩個引數都設為1來執行,那麼最後也就和我寫的程式一樣,只有兩個執行緒,結果照樣有不同步.這又怎麼解釋呢

回覆人:tianfeichen(側耳傾聽) ( ) 信譽:110  2003-01-22 23:57:00  得分:0
執行緒的安排畢竟是隨機的,很少會有不同步的出現,次數少了不容易發現。
我常用的方法是,先讓無限迴圈開始,迴圈的時候也不要求輸出什麼的,只加上一個停止的條件,比如:
if (counter1 != counter2)
{
  System.out.println(counter1 + " ," + counter2)
  System.exit(0);
}
剩下的就是等了,一般十幾秒甚至幾秒就出結果了,可以發現記數已經到幾十萬或者幾百萬了。
如果同時開了5個執行緒,等了一分鐘,我就算是它同步了。

我的方法可能不太科學,不過效果挺好。

回覆人: xm4014(forrest) ( ) 信譽:100  2003-01-23 11:44:00  得分:0
可以幫我一下嗎?為什麼我按照你的方法卻始終沒有得到結果呢?
將下面的程式碼直接複製就可以了,程式名為Sharing2.java,版本是1.4.1
class TwoCounter extends Thread {
  private int count1 = 0, count2 = 0;
  private boolean started=false;
  public void start(){
  if (!started)
  {
  started=true;
  super.start();
  }
  }
  public void run() {
  while (true) {
  count1++;
  count2++;
//  System.out.println("Count1="+count1+",Count2="+count2);
  try {
  sleep(500);
  } catch (InterruptedException e){System.out.println("TwoCounter.run");}
  }
  }

  public void synchTest() {
//  Sharing2.incrementAccess();
  if(count1 != count2)
  {System.out.println(count1+","+count2);
  System.exit(0);
  }
  }
}

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){System.out.println("Watcher.run");}
  }
  }
}

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();
  new Watcher(aaa);
  }
} ///:~

另外,根據你的意思,我的程式是沒有問題的,只是執行緒少了,不同步很難產生,要等到counter增加到很大數目的時候才有可能,對嗎?

回覆人: hey_you(Hey) ( ) 信譽:100  2003-01-23 13:27:00  得分:0

我是這樣想的:不同步而發生衝突是一種可能性,而sychronize是讓這種可能性為0。你沒有1發現不同步,並不能證明永遠都不會發生不同步的情況,那只是一個時間問題。對執行緒的排程受了環境的影響,要是你機器上同時還跑了很多程式,可能情況就不同了。

回覆人: xm4014(forrest) ( ) 信譽:100  2003-01-23 15:56:00  得分:0
呵呵,我用tianfeichen(側耳傾聽)的方法執行的程式,也就是我上面貼的程式碼居然有結果了,counter1= 217327,counter2=217356,還真想差的不少。但是時間上絕不是一兩分鐘那麼簡單,至少過了兩個小時,可能真是我和他的執行環境的不同造成的.正如hey_you(Hey)所說,只是一個時間問題.

希望其它人能給出更多的看法,如果覺得沒必要再討論下去,那我就接貼.

回覆人: bluesmile979(笑著) ( ) 信譽:100  2003-01-23 16:38:00  得分:0
我考,一兩個小時你也能堅持,服了。

我認為問題結果也就兩點了。一個就是我認為的執行緒數量

另一個就是你認為的setText會有比較多的處理,佔用比較多的資源。

兩種情況都會影響到這個問題的出現機率:)樓主宰總結一下吧,呵呵。

回覆人: linliangyi(藍山咖啡) ( ) 信譽:100  2003-01-23 17:10:00  得分:0
sleep(500)佔用的時間勝過for(5000)的時間,因此執行緒在sleep中被切換的機率遠勝於在for中被中斷的機率!!(回頭去看我的程式就知道了)

事實上,兩個變數從不相等變為相等,正是說明了不同步!!

順便說一下關於和awt線上程中操作時,比如setText,常造成很多意外
樓主可以看看相關的書籍!!

回覆人: xm4014(forrest) ( ) 信譽:100  2003-01-24 14:25:00  得分:0
我將各位的觀點綜合起來總結一下:

首先要肯定的是,假如不使用synchronized關鍵字來定義同步方法或者定義同步塊,那麼,發生不同步的可能是絕對存在的,反過來說,synchronized就是讓這種可能性為0.

在第一種情況下,發生不同步的可能雖然存在,但是它的機率受到以下幾個方面因素的影響
1.在不同的及執行環境下,捕捉到不同步的機率可能就不一樣,或者說等待的時間可能就有長有短
2.程式中執行緒數目的多寡,如果執行緒太少,那麼這種不同步就難於捕捉到,可能需要等待很長的時間
3.程式碼本身的影響.比如使用awt類中涉及到GUI的方法,可能就會佔用較多的資源,造成很多意外,那麼發生衝突的可能性就大得多
4.執行緒是由作業系統隨機分配的,本來就存在著不確定性,這種不確定性也會影響最後的結果

不知道是否正確,大家還有什麼補充呢?
明天正式結帖

不過說實話,我有點搞不懂,為什麼最後的結果,counter1(217327)和counter2(217356)會相差那麼多呢.按照我的程式,即便watcher執行緒插到兩個自加的語句中間來,檢測到的這兩個計數器之間的差異頂多也就是1啊.出現這麼大的差異,只可能是某一個計數器的自加語句有好多次在根本沒有執行的情況下就被強行中斷了.這就太恐怖了!雖然有其它執行緒的存在會干擾當前執行緒,但是也不至於讓當前執行緒語句不執行吧,最多也就是等等再執行啊?我有點糊塗了,作業系統沒學好,如果大家不嫌麻煩,清幫我解釋一下吧

結果現在又有新的問題,我想又要等到明天才有答案吧

但我們今天可以解決另一個涉及到synchronized的問題.這是我在論壇上看到的一個貼子.正是因為我解決不了,我才認為有必要回頭來好好研究執行緒和同步等內容的.
問題如下:

析這段程式,並解釋一下,著重講講synchronized、wait(),notify 謝謝!
class ThreadA
{
  public static void main(String[] args)
  {
  Threa b=new ThreadB();
  b.start();
  System.out.println("b is start....");
  synchronized(b)//括號裡的b是什麼意思,起什麼作用?
  {
  try
  {
 System.out.println("Waiting for b to complete...");
 b.wait();//這一句是什麼意思,究竟讓誰wait?
  System.out.println("Completed.Now back to main thread");
  }catch (InterruptedException e){}
  }
  System.out.println("Total is :"+b.total);
  }
}


class ThreadB extends Thread
{
  int total;
  public void run()
  {
  synchronized(this)
  {
  System.out.println("ThreadB is running..");
  for (int i=0;i<100;i++ )
  {
  total +=i;
  System.out.println("total is "+total);
  }
  notify();
  }
  }
}

要分析這個程式,首先要理解notify()和wait(),為什麼在前幾天紀錄執行緒的時候沒有紀錄這兩個方法呢,因為這兩個方法本來就不屬於Thread類,而是屬於最底層的基礎類的,也就是說不光是Thread,每個都有notify和wait的功能,為什麼?因為他們是用來操縱鎖的,而每個物件都有鎖,鎖是每個物件的基礎,既然鎖是基礎的,那麼操縱鎖的方法當然也是最基礎了.

 再往下看之前呢,首先最好複習一下Think in Java的14.3.1中第3部分內容:等待和通知,也就是wait()和notify了.

按照Think in Java中的解釋:"wait()允許我們將執行緒置入“睡眠”狀態,同時又“積極”地等待條件發生改變.而且只有在一個notify()或notifyAll()發生變化的時候,執行緒才會被喚醒,並檢查條件是否有變."

  我們來解釋一下這句話.
  "wait()允許我們將執行緒置入“睡眠”狀態",也就是說,wait也是讓當前執行緒阻塞的,這一點和sleep或者suspend是相同的.那和sleep,suspend有什麼區別呢?

  區別在於"(wait)同時又“積極”地等待條件發生改變",這一點很關鍵,sleep和suspend無法做到.因為我們有時候需要透過同步(synchronized)的幫助來防止執行緒之間的衝突,而一旦使用同步,就要鎖定物件,也就是獲取物件鎖,其它要使用該物件鎖的執行緒都只能排隊等著,等到同步方法或者同步塊裡的程式全部執行完才有機會.在同步方法和同步塊中,無論sleep()還是suspend()都不可能自己被的時候解除鎖定,他們都霸佔著正在使用的物件鎖不放.
  而wait卻可以,它可以讓同步方法或者同步塊暫時放棄物件鎖,而將它暫時讓給其它需要物件鎖的人(這裡應該是程式塊,或執行緒)用,這意味著可在wait()期間呼叫執行緒物件中的其他同步方法!在其它情況下(sleep啊,suspend啊),這是不可能的.
  但是注意我前面說的,只是暫時放棄物件鎖,暫時給其它執行緒使用,我wait所在的執行緒還是要把這個物件鎖收回來的呀.wait什麼?就是wait別人用完了還給我啊!
  好,那怎麼把物件鎖收回來呢?
  第一種方法,限定借出去的時間.在wait()中設定引數,比如wait(1000),以毫秒為單位,就表明我只借出去1秒中,一秒鐘之後,我自動收回.
  第二種方法,讓借出去的人通知我,他用完了,要還給我了.這時,我馬上就收回來.哎,假如我設了1小時之後收回,別人只用了半小時就完了,那怎麼辦呢?靠!當然用完了就收回了,還管我設的是多長時間啊.

  那麼別人怎麼通知我呢?相信大家都可以想到了,notify(),這就是最後一句話"而且只有在一個notify()或notifyAll()發生變化的時候,執行緒才會被喚醒"的意思了.
  因此,我們可將一個wait()和notify()置入任何同步方法或同步塊內部,無論在那個類裡是否準備進行涉及執行緒的處理。而且實際上,我們也只能在同步方法或者同步塊裡面呼叫wait()和notify().

  這個時候我們來解釋上面的程式,簡直是易如反掌了.

  synchronized(b){...};的意思是定義一個同步塊,使用b作為資源鎖。b.wait();的意思是臨時釋放鎖,並阻塞當前執行緒,好讓其他使用同一把鎖的執行緒有機會執行,在這裡要用同一把鎖的就是b執行緒本身.這個執行緒在執行到一定地方後用notify()通知wait的執行緒,鎖已經用完,待notify()所在的同步塊執行完之後,wait所在的執行緒就可以繼續執行.


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-993918/,如需轉載,請註明出處,否則將追究法律責任。

相關文章