java something

          不要以為......很遙遠(yuǎn)
          隨筆 - 23, 文章 - 1, 評(píng)論 - 2, 引用 - 0
          數(shù)據(jù)加載中……

          線程的同步與共享

          線程的同步與共享

          前面程序中的線程都是獨(dú)立的、異步執(zhí)行的線程。但在很多情況下,多個(gè)線程需要共享數(shù)據(jù)資源,這就涉及到線程的同步與資源共享的問(wèn)題。

          資源沖突

          下面的例子說(shuō)明,多個(gè)線程共享資源,如果不加以控制可能會(huì)產(chǎn)生沖突。

          程序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()方法中創(chuàng)建了兩個(gè)線程類Counter的對(duì)象count1count2,這兩個(gè)對(duì)象共享一個(gè)Num類的對(duì)象num。兩個(gè)線程對(duì)象開始運(yùn)行后,都調(diào)用同一個(gè)對(duì)象numincrease()方法來(lái)增加num對(duì)象的xy的值。在main()方法的for()循環(huán)中輸出num對(duì)象的xy的值。程序輸出結(jié)果有些xy的值相等,大部分xy的值不相等。

          出現(xiàn)上述情況的原因是:兩個(gè)線程對(duì)象同時(shí)操作一個(gè)num對(duì)象的同一段代碼,通常將這段代碼段稱為臨界區(qū)(critical sections)。在線程執(zhí)行時(shí),可能一個(gè)線程執(zhí)行了x++語(yǔ)句而尚未執(zhí)行y++語(yǔ)句時(shí),系統(tǒng)調(diào)度另一個(gè)線程對(duì)象執(zhí)行x++y++,這時(shí)在主線程中調(diào)用testEqual()方法輸出x、y的值不相等

          對(duì)象鎖的實(shí)現(xiàn)

          上述程序的運(yùn)行結(jié)果說(shuō)明了多個(gè)線程訪問(wèn)同一個(gè)對(duì)象出現(xiàn)了沖突,為了保證運(yùn)行結(jié)果正確(x、y的值總相等),可以使用Java語(yǔ)言的synchronized關(guān)鍵字,用該關(guān)鍵字修飾方法。用synchronized關(guān)鍵字修飾的方法稱為同步方法,Java平臺(tái)為每個(gè)具有synchronized代碼段的對(duì)象關(guān)聯(lián)一個(gè)對(duì)象鎖(object lock)。這樣任何線程在訪問(wèn)對(duì)象的同步方法時(shí),首先必須獲得對(duì)象鎖,然后才能進(jìn)入synchronized方法,這時(shí)其他線程就不能再同時(shí)訪問(wèn)該對(duì)象的同步方法了(包括其他的同步方法)。

          通常有兩種方法實(shí)現(xiàn)對(duì)象鎖:

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

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

          synchronized void increase(){

              x++;

              y++;

          }

          synchronized void testEqual(){

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

          }

          一個(gè)方法使用synchronized關(guān)鍵字修飾后,當(dāng)一個(gè)線程調(diào)用該方法時(shí),必須先獲得對(duì)象鎖,只有在獲得對(duì)象鎖以后才能進(jìn)入synchronized方法。一個(gè)時(shí)刻對(duì)象鎖只能被一個(gè)線程持有。如果對(duì)象鎖正在被一個(gè)線程持有,其他線程就不能獲得該對(duì)象鎖,其他線程就必須等待持有該對(duì)象鎖的線程釋放鎖。

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

          如果只為increase()方法添加synchronized 關(guān)鍵字,結(jié)果還會(huì)出現(xiàn)x、y的值不相等的情況.
              
              (2)
          前面實(shí)現(xiàn)對(duì)象鎖是在方法前加上synchronized 關(guān)鍵字,這對(duì)于我們自己定義的類很容易實(shí)現(xiàn),但如果使用類庫(kù)中的類或別人定義的類在調(diào)用一個(gè)沒(méi)有使用synchronized關(guān)鍵字修飾的方法時(shí),又要獲得對(duì)象鎖,可以使用下面的格式:

          synchronized(object){

             //方法調(diào)用

          }

          假如Num類的increase()方法沒(méi)有使用synchronized 關(guān)鍵字,我們?cè)诙xCounter類的run()方法時(shí)可以按如下方法使用synchronized為部分代碼加鎖。

          public void run(){

              while(true){

          synchronized (num){

                 num.increase();

               }

              }

          }

          同時(shí)在main()方法中調(diào)用testEqual()方法也用synchronized關(guān)鍵字修飾,這樣得到的結(jié)果相同。

          synchronized(num){

              num.testEqual();

          }

          對(duì)象鎖的獲得和釋放是由Java運(yùn)行時(shí)系統(tǒng)自動(dòng)完成的。

          每個(gè)類也可以有類鎖。類鎖控制對(duì)類的synchronized static代碼的訪問(wèn)。請(qǐng)看下面的例子:

          public class X{

           static int x, y;

           static synchronized void foo(){

               x++;

          y++;

          }

          }

          當(dāng)foo()方法被調(diào)用時(shí),調(diào)用線程必須獲得X類的類鎖。

          3  線程間的同步控制

          在多線程的程序中,除了要防止資源沖突外,有時(shí)還要保證線程的同步。下面通過(guò)生產(chǎn)者-消費(fèi)者模型來(lái)說(shuō)明線程的同步與資源共享的問(wèn)題。

          假設(shè)有一個(gè)生產(chǎn)者(Producer),一個(gè)消費(fèi)者(Consumer)。生產(chǎn)者產(chǎn)生0~9的整數(shù),將它們存儲(chǔ)在倉(cāng)庫(kù)(CubbyHole)的對(duì)象中并打印出這些數(shù)來(lái);消費(fèi)者從倉(cāng)庫(kù)中取出這些整數(shù)并將其也打印出來(lái)。同時(shí)要求生產(chǎn)者產(chǎn)生一個(gè)數(shù)字,消費(fèi)者取得一個(gè)數(shù)字,這就涉及到兩個(gè)線程的同步問(wèn)題。

          這個(gè)問(wèn)題就可以通過(guò)兩個(gè)線程實(shí)現(xiàn)生產(chǎn)者和消費(fèi)者,它們共享CubbyHole一個(gè)對(duì)象。如果不加控制就得不到預(yù)期的結(jié)果。

          1. 不同步的設(shè)計(jì)

          首先我們?cè)O(shè)計(jì)用于存儲(chǔ)數(shù)據(jù)的類,該類的定義如下:

          程序 CubbyHole.java

          class CubbyHole{

            private int content ;

          public synchronized void put(int value){

          content = value;

          }

            public synchronized int get(){

          return content ;

          }

          }

          _____________________________________________________________________________

          CubbyHole類使用一個(gè)私有成員變量content用來(lái)存放整數(shù),put()方法和get()方法用來(lái)設(shè)置變量content的值。CubbyHole對(duì)象為共享資源,所以用synchronized關(guān)鍵字修飾。當(dāng)put()方法或get()方法被調(diào)用時(shí),線程即獲得了對(duì)象鎖,從而可以避免資源沖突。

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

          接下來(lái)我們看ProducerConsumer的定義,這兩個(gè)類的定義如下:

          程序 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類中定義了一個(gè)CubbyHole類型的成員變量cubbyhole,它用來(lái)存儲(chǔ)產(chǎn)生的整數(shù),另一個(gè)成員變量number用來(lái)記錄線程號(hào)。這兩個(gè)變量通過(guò)構(gòu)造方法傳遞得到。在該類的run()方法中,通過(guò)一個(gè)循環(huán)產(chǎn)生10個(gè)整數(shù),每次產(chǎn)生一個(gè)整數(shù),調(diào)用cubbyhole對(duì)象的put()方法將其存入該對(duì)象中,同時(shí)輸出該數(shù)。

          下面是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()方法中也是一個(gè)循環(huán),每次調(diào)用cubbyholeget()方法返回當(dāng)前存儲(chǔ)的整數(shù),然后輸出。

          下面是主程序,在該程序的main()方法中創(chuàng)建一個(gè)CubbyHole對(duì)象c,一個(gè)Producer對(duì)象p1,一個(gè)Consumer對(duì)象c1,然后啟動(dòng)兩個(gè)線程。

          程序 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();

              }

          }

          _____________________________________________________________________________

          該程序中對(duì)CubbyHole類的設(shè)計(jì),盡管使用了synchronized關(guān)鍵字實(shí)現(xiàn)了對(duì)象鎖,但這還不夠。程序運(yùn)行可能出現(xiàn)下面兩種情況:

          如果生產(chǎn)者的速度比消費(fèi)者快,那么在消費(fèi)者來(lái)不及取前一個(gè)數(shù)據(jù)之前,生產(chǎn)者又產(chǎn)生了新的數(shù)據(jù),于是消費(fèi)者很可能會(huì)跳過(guò)前一個(gè)數(shù)據(jù),這樣就會(huì)產(chǎn)生下面的結(jié)果:

          Consumer: 3

          Producer: 4

          Producer: 5

          Consumer: 5

          反之,如果消費(fèi)者比生產(chǎn)者快,消費(fèi)者可能兩次取同一個(gè)數(shù)據(jù),可能產(chǎn)生下面的結(jié)果:

          Producer: 4

          Consumer: 4

          Consumer: 4

          Producer: 5

          2. 監(jiān)視器模型

          為了避免上述情況發(fā)生,就必須使生產(chǎn)者線程向CubbyHole對(duì)象中存儲(chǔ)數(shù)據(jù)與消費(fèi)者線程從CubbyHole對(duì)象中取得數(shù)據(jù)同步起來(lái)。為了達(dá)到這一目的,在程序中可以采用監(jiān)視器(monitor)模型,同時(shí)通過(guò)調(diào)用對(duì)象的wait()方法和notify()方法實(shí)現(xiàn)同步。

          下面是修改后的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;

          }

          }

          _____________________________________________________________________________

          這里有一個(gè)boolean型的私有成員變量available用來(lái)指示內(nèi)容是否可取。當(dāng)availabletrue時(shí)表示數(shù)據(jù)已經(jīng)產(chǎn)生還沒(méi)被取走,當(dāng)availablefalse時(shí)表示數(shù)據(jù)已被取走還沒(méi)有存放新的數(shù)據(jù)。

          當(dāng)生產(chǎn)者線程進(jìn)入put()方法時(shí),首先檢查available的值,若其為false,才可執(zhí)行put()方法,若其為true,說(shuō)明數(shù)據(jù)還沒(méi)有被取走,該線程必須等待。因此在put()方法中調(diào)用CubbyHole對(duì)象的wait()方法等待。調(diào)用對(duì)象的wait()方法使線程進(jìn)入等待狀態(tài),同時(shí)釋放對(duì)象鎖。直到另一個(gè)線程對(duì)象調(diào)用了notify()notifyAll()方法,該線程才可恢復(fù)運(yùn)行。

          類似地,當(dāng)消費(fèi)者線程進(jìn)入get()方法時(shí),也是先檢查available的值,若其為true,才可執(zhí)行get()方法,若其為false,說(shuō)明還沒(méi)有數(shù)據(jù),該線程必須等待。因此在get()方法中調(diào)用CubbyHole對(duì)象的wait()方法等待。調(diào)用對(duì)象的wait()方法使線程進(jìn)入等待狀態(tài),同時(shí)釋放對(duì)象鎖。

          上述過(guò)程就是監(jiān)視器模型,其中CubbyHole對(duì)象為監(jiān)視器。通過(guò)監(jiān)視器模型可以保證生產(chǎn)者線程和消費(fèi)者線程同步,結(jié)果正確。

          程序的運(yùn)行結(jié)果如下:

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

          ·         public final void wait()

          ·         public final void wait(long timeout)

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

          當(dāng)前線程必須具有對(duì)象監(jiān)視器的鎖,當(dāng)調(diào)用該方法時(shí)線程釋放監(jiān)視器的鎖。調(diào)用這些方法使當(dāng)前線程進(jìn)入等待(阻塞)狀態(tài),直到另一個(gè)線程調(diào)用了該對(duì)象的notify()方法或notifyAll()方法,該線程重新進(jìn)入運(yùn)行狀態(tài),恢復(fù)執(zhí)行。

          timeoutnanos為等待的時(shí)間的毫秒和納秒,當(dāng)時(shí)間到或其他對(duì)象調(diào)用了該對(duì)象的notify()方法或notifyAll()方法,該線程重新進(jìn)入運(yùn)行狀態(tài),恢復(fù)執(zhí)行。

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

          ·         public final void notify()

          ·         public final void notifyAll()

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

              在生產(chǎn)者/消費(fèi)者的例子中,CubbyHole類的putget方法就是臨界區(qū)。當(dāng)生產(chǎn)者修改它時(shí),消費(fèi)者不能問(wèn)CubbyHole對(duì)象;當(dāng)消費(fèi)者取得值時(shí),生產(chǎn)者也不能修改它。








           

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


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 新巴尔虎右旗| 济南市| 南平市| 渭源县| 博野县| 东乡县| 杭锦后旗| 八宿县| 吉林省| 桐柏县| 古丈县| 贵南县| 怀安县| 赣榆县| 全南县| 平阳县| 北安市| 勃利县| 博客| 子洲县| 肇州县| 白朗县| 定结县| 潜山县| 石柱| 宁远县| 兴和县| 久治县| 本溪市| 凯里市| 大石桥市| 和硕县| 新乡县| 奇台县| 长兴县| 缙云县| 昌乐县| 新余市| 揭东县| 陇川县| 堆龙德庆县|