java something

          不要以為......很遙遠
          隨筆 - 23, 文章 - 1, 評論 - 2, 引用 - 0
          數據加載中……

          線程的同步與共享

          線程的同步與共享

          前面程序中的線程都是獨立的、異步執行的線程。但在很多情況下,多個線程需要共享數據資源,這就涉及到線程的同步與資源共享的問題。

          資源沖突

          下面的例子說明,多個線程共享資源,如果不加以控制可能會產生沖突。

          程序CounterTest.java

           

          class Num
          {
           private int x = 0;
           private int y = 0;

           void increase()
           {
            x++;
            y++;
           }

           void testEqual()
           {
            System.out.println(x + "," + y + ":" + (x == y));
           }
          }

          class Counter extends Thread
          {
           private Num num;

           Counter(Num num)
           {
            this.num = num;
           }

           public void run()
           {
            while (true)
            {
             num.increase();
            }
           }
          }

          public class CounterTest
          {
           public static void main(String[] args)
           {
            Num num = new Num();
            Thread count1 = new Counter(num);
            Thread count2 = new Counter(num);
            count1.start();
            count2.start();

            for (int i = 0; i < 100; i++)
            {
             num.testEqual();
             try
             {
              Thread.sleep(100);
             } catch (InterruptedException e)
             {
             }
            }
           }
          }

           

           

          上述程序在CounterTest類的main()方法中創建了兩個線程類Counter的對象count1count2,這兩個對象共享一個Num類的對象num。兩個線程對象開始運行后,都調用同一個對象numincrease()方法來增加num對象的xy的值。在main()方法的for()循環中輸出num對象的xy的值。程序輸出結果有些xy的值相等,大部分xy的值不相等。

          出現上述情況的原因是:兩個線程對象同時操作一個num對象的同一段代碼,通常將這段代碼段稱為臨界區(critical sections)。在線程執行時,可能一個線程執行了x++語句而尚未執行y++語句時,系統調度另一個線程對象執行x++y++,這時在主線程中調用testEqual()方法輸出xy的值不相等

          對象鎖的實現

          上述程序的運行結果說明了多個線程訪問同一個對象出現了沖突,為了保證運行結果正確(xy的值總相等),可以使用Java語言的synchronized關鍵字,用該關鍵字修飾方法。用synchronized關鍵字修飾的方法稱為同步方法,Java平臺為每個具有synchronized代碼段的對象關聯一個對象鎖(object lock)。這樣任何線程在訪問對象的同步方法時,首先必須獲得對象鎖,然后才能進入synchronized方法,這時其他線程就不能再同時訪問該對象的同步方法了(包括其他的同步方法)

          通常有兩種方法實現對象鎖:

          (1) 在方法的聲明中使用synchronized關鍵字,表明該方法為同步方法。

          對于上面的程序我們可以在定義Num類的increase()testEqual()方法時,在它們前面加上synchronized關鍵字,如下所示:

          synchronized void increase(){

              x++;

              y++;

          }

          synchronized void testEqual(){

              System.out.println(x+","+y+":"+(x==y)+":"+(x<y));

          }

          一個方法使用synchronized關鍵字修飾后,當一個線程調用該方法時,必須先獲得對象鎖,只有在獲得對象鎖以后才能進入synchronized方法。一個時刻對象鎖只能被一個線程持有。如果對象鎖正在被一個線程持有,其他線程就不能獲得該對象鎖,其他線程就必須等待持有該對象鎖的線程釋放鎖。

          如果類的方法使用了synchronized關鍵字修飾,則稱該類對象是線程安全的,否則是線程不安全的。

          如果只為increase()方法添加synchronized 關鍵字,結果還會出現xy的值不相等的情況.
              
              (2)
          前面實現對象鎖是在方法前加上synchronized 關鍵字,這對于我們自己定義的類很容易實現,但如果使用類庫中的類或別人定義的類在調用一個沒有使用synchronized關鍵字修飾的方法時,又要獲得對象鎖,可以使用下面的格式:

          synchronized(object){

             //方法調用

          }

          假如Num類的increase()方法沒有使用synchronized 關鍵字,我們在定義Counter類的run()方法時可以按如下方法使用synchronized為部分代碼加鎖。

          public void run(){

              while(true){

          synchronized (num){

                 num.increase();

               }

              }

          }

          同時在main()方法中調用testEqual()方法也用synchronized關鍵字修飾,這樣得到的結果相同。

          synchronized(num){

              num.testEqual();

          }

          對象鎖的獲得和釋放是由Java運行時系統自動完成的。

          每個類也可以有類鎖。類鎖控制對類的synchronized static代碼的訪問。請看下面的例子:

          public class X{

           static int x, y;

           static synchronized void foo(){

               x++;

          y++;

          }

          }

          foo()方法被調用時,調用線程必須獲得X類的類鎖。

          3  線程間的同步控制

          在多線程的程序中,除了要防止資源沖突外,有時還要保證線程的同步。下面通過生產者-消費者模型來說明線程的同步與資源共享的問題。

          假設有一個生產者(Producer),一個消費者(Consumer)。生產者產生0~9的整數,將它們存儲在倉庫(CubbyHole)的對象中并打印出這些數來;消費者從倉庫中取出這些整數并將其也打印出來。同時要求生產者產生一個數字,消費者取得一個數字,這就涉及到兩個線程的同步問題。

          這個問題就可以通過兩個線程實現生產者和消費者,它們共享CubbyHole一個對象。如果不加控制就得不到預期的結果。

          1. 不同步的設計

          首先我們設計用于存儲數據的類,該類的定義如下:

          程序 CubbyHole.java

          class CubbyHole{

            private int content ;

          public synchronized void put(int value){

          content = value;

          }

            public synchronized int get(){

          return content ;

          }

          }

          _____________________________________________________________________________

          CubbyHole類使用一個私有成員變量content用來存放整數,put()方法和get()方法用來設置變量content的值。CubbyHole對象為共享資源,所以用synchronized關鍵字修飾。當put()方法或get()方法被調用時,線程即獲得了對象鎖,從而可以避免資源沖突。

          這樣當Producer對象調用put()方法是,它鎖定了該對象,Consumer對象就不能調用get()方法。當put()方法返回時,Producer對象釋放了CubbyHole的鎖。類似地,當Consumer對象調用CubbyHoleget()方法時,它也鎖定該對象,防止Producer對象調用put()方法。

          接下來我們看ProducerConsumer的定義,這兩個類的定義如下:

          程序 Producer.java

          public class Producer extends Thread {

              private CubbyHole cubbyhole;

              private int number;

              public Producer(CubbyHole c, int number) {

                  cubbyhole = c;

                  this.number = number;

              }

              public void run() {

                 for (int i = 0; i < 10; i++) {

                    cubbyhole.put(i);

                    System.out.println("Producer #" + this.number + " put: " + i);

                    try {

                          sleep((int)(Math.random() * 100));

                     } catch (InterruptedException e) { }

                  }

              }

          }

          _____________________________________________________________________________

          Producer類中定義了一個CubbyHole類型的成員變量cubbyhole,它用來存儲產生的整數,另一個成員變量number用來記錄線程號。這兩個變量通過構造方法傳遞得到。在該類的run()方法中,通過一個循環產生10個整數,每次產生一個整數,調用cubbyhole對象的put()方法將其存入該對象中,同時輸出該數。

          下面是Consumer類的定義:

          程序 Consumer.java

          public class Consumer extends Thread {

              private CubbyHole cubbyhole;

              private int number;

              public Consumer(CubbyHole c, int number) {

                  cubbyhole = c;

                  this.number = number;

              }

              public void run() {

                  int value = 0;

                  for (int i = 0; i < 10; i++) {

                      value = cubbyhole.get();

                System.out.println("Consumer #" + this.number + " got: " + value);

                  }

              }

          }

          _____________________________________________________________________________

          Consumer類的run()方法中也是一個循環,每次調用cubbyholeget()方法返回當前存儲的整數,然后輸出。

          下面是主程序,在該程序的main()方法中創建一個CubbyHole對象c,一個Producer對象p1,一個Consumer對象c1,然后啟動兩個線程。

          程序 ProducerConsumerTest.java

          public class ProducerConsumerTest {

              public static void main(String[] args) {

                  CubbyHole c = new CubbyHole();

                  Producer p1 = new Producer(c, 1);

                  Consumer c1 = new Consumer(c, 1);

                  p1.start();

                  c1.start();

              }

          }

          _____________________________________________________________________________

          該程序中對CubbyHole類的設計,盡管使用了synchronized關鍵字實現了對象鎖,但這還不夠。程序運行可能出現下面兩種情況:

          如果生產者的速度比消費者快,那么在消費者來不及取前一個數據之前,生產者又產生了新的數據,于是消費者很可能會跳過前一個數據,這樣就會產生下面的結果:

          Consumer: 3

          Producer: 4

          Producer: 5

          Consumer: 5

          反之,如果消費者比生產者快,消費者可能兩次取同一個數據,可能產生下面的結果:

          Producer: 4

          Consumer: 4

          Consumer: 4

          Producer: 5

          2. 監視器模型

          為了避免上述情況發生,就必須使生產者線程向CubbyHole對象中存儲數據與消費者線程從CubbyHole對象中取得數據同步起來。為了達到這一目的,在程序中可以采用監視器(monitor)模型,同時通過調用對象的wait()方法和notify()方法實現同步。

          下面是修改后的CubbyHole類的定義:

          程序CubbyHole.java

          class CubbyHole{

            private int content ;

            private boolean available=false;

           

          public synchronized void put(int value){

           while(available==true){

                try{

          wait();

          }catch(InterruptedException e){}

          }

          content =value;

          available=true;

          notifyAll();

          }

            public synchronized int get(){

              while(available==false){

                try{

          wait();

          }catch(InterruptedException e){}

              }

          available=false;

          notifyAll();

          return content;

          }

          }

          _____________________________________________________________________________

          這里有一個boolean型的私有成員變量available用來指示內容是否可取。當availabletrue時表示數據已經產生還沒被取走,當availablefalse時表示數據已被取走還沒有存放新的數據。

          當生產者線程進入put()方法時,首先檢查available的值,若其為false,才可執行put()方法,若其為true,說明數據還沒有被取走,該線程必須等待。因此在put()方法中調用CubbyHole對象的wait()方法等待。調用對象的wait()方法使線程進入等待狀態,同時釋放對象鎖。直到另一個線程對象調用了notify()notifyAll()方法,該線程才可恢復運行。

          類似地,當消費者線程進入get()方法時,也是先檢查available的值,若其為true,才可執行get()方法,若其為false,說明還沒有數據,該線程必須等待。因此在get()方法中調用CubbyHole對象的wait()方法等待。調用對象的wait()方法使線程進入等待狀態,同時釋放對象鎖。

          上述過程就是監視器模型,其中CubbyHole對象為監視器。通過監視器模型可以保證生產者線程和消費者線程同步,結果正確。

          程序的運行結果如下:

          特別注意:wait()notify()notifyAll()方法是Object類定義的方法,并且這些方法只能用在synchronized代碼段中。它們的定義格式如下:

          ·         public final void wait()

          ·         public final void wait(long timeout)

          ·         public final void wait(long timeout, int nanos)

          當前線程必須具有對象監視器的鎖,當調用該方法時線程釋放監視器的鎖。調用這些方法使當前線程進入等待(阻塞)狀態,直到另一個線程調用了該對象的notify()方法或notifyAll()方法,該線程重新進入運行狀態,恢復執行。

          timeoutnanos為等待的時間的毫秒和納秒,當時間到或其他對象調用了該對象的notify()方法或notifyAll()方法,該線程重新進入運行狀態,恢復執行。

          wait()的聲明拋出了InterruptedException,因此程序中必須捕獲或聲明拋出該異常。

          ·         public final void notify()

          ·         public final void notifyAll()

          喚醒處于等待該對象鎖的一個或所有的線程繼續執行,通常使用notifyAll()方法。

              在生產者/消費者的例子中,CubbyHole類的putget方法就是臨界區。當生產者修改它時,消費者不能問CubbyHole對象;當消費者取得值時,生產者也不能修改它。








           

          posted on 2011-09-02 01:38 Jamie 閱讀(484) 評論(0)  編輯  收藏 所屬分類: 多線程

          主站蜘蛛池模板: 蓬安县| 盱眙县| 任丘市| 淮南市| 获嘉县| 康平县| 琼结县| 宜黄县| 筠连县| 廊坊市| 平潭县| 蚌埠市| 青岛市| 临沂市| 渑池县| 江源县| 东城区| 六枝特区| 北流市| 宜阳县| 扬州市| 岢岚县| 江川县| 沾化县| 陵川县| 车险| 乐陵市| 平安县| 邳州市| 陆良县| 镇江市| 从江县| 北安市| 咸宁市| 财经| 梨树县| 绥滨县| 滨州市| 洪江市| 扶沟县| 谷城县|