John Jiang

          a cup of Java, cheers!
          https://github.com/johnshajiang/blog

             :: 首頁(yè) ::  :: 聯(lián)系 :: 聚合  :: 管理 ::
            131 隨筆 :: 1 文章 :: 530 評(píng)論 :: 0 Trackbacks
          Java Tutorials -- Concurrency
              近一段時(shí)間在使用Thinking in Java(4th, English)和Java Concurrency in Practice學(xué)習(xí)Java并發(fā)編程。不得不說(shuō)官方的Java Tutorias是很好的Java并發(fā)編程入門級(jí)教程,故將它其中的Concurrency一章翻譯在了此處。與我翻譯Java Tutorias中Generics一章時(shí)的目的相同,只是對(duì)自己近一段時(shí)間學(xué)習(xí)的回顧罷了,也希望對(duì)其它朋友能有所助益。(2007.11.29最后更新)

          課程: 并發(fā)
              計(jì)算機(jī)用戶們將他們的系統(tǒng)能夠在同一時(shí)刻做一件以上的事情視為一種當(dāng)然。他們猜想著,他們?cè)谑褂米痔幚砥鞯耐瑫r(shí)其它的應(yīng)用程序正在下載文件,管理打印隊(duì)列和傳輸音頻流。例如,音頻流應(yīng)用程序必須在同一時(shí)刻從網(wǎng)絡(luò)上讀取數(shù)字音頻數(shù)據(jù),解壓它們,管理重放功能,并更新它們的顯示方式。
              Java平臺(tái)徹底地被設(shè)計(jì)為支持并發(fā)的編程,它有著在Java程序設(shè)計(jì)語(yǔ)言和Java字節(jié)類庫(kù)中的基本并發(fā)支持。從5.0開(kāi)始,Java平臺(tái)也將包含著高級(jí)的并發(fā)API。該課程介紹了該平臺(tái)的基本并發(fā)編程支持,并概述了java.util.concurrent包中的一些高級(jí)API。

          進(jìn)程與線程
              在并發(fā)編程中,有兩種基本的執(zhí)行單元:進(jìn)程和線程。在Java程序設(shè)計(jì)語(yǔ)言中,并發(fā)編程幾乎只關(guān)心線程。當(dāng)然,進(jìn)程也是重要的。
              計(jì)算機(jī)系統(tǒng)一般都有很多活躍的進(jìn)程與線程。甚至在只有一個(gè)執(zhí)行內(nèi)核的系統(tǒng)中也是如此。在一個(gè)給定的時(shí)刻,確實(shí)只能有一個(gè)線程是在執(zhí)行。通過(guò)一種稱之為"分片"的操作系統(tǒng)特性,進(jìn)程與線程共享著單核的處理時(shí)間。
              計(jì)算機(jī)系統(tǒng)擁有多個(gè)處理器或有多個(gè)執(zhí)行內(nèi)核的單處理器正在變得越來(lái)越普遍。這將很大地提升系統(tǒng)在處理進(jìn)程與線程的并發(fā)執(zhí)行時(shí)的能力。
          進(jìn)程
              一個(gè)進(jìn)程擁有一個(gè)自我包括的執(zhí)行環(huán)境。一個(gè)進(jìn)程一般擁有一個(gè)完整的,內(nèi)置的基本運(yùn)行時(shí)資源;特別地,每個(gè)進(jìn)程都擁有它自己的內(nèi)存空間。
              進(jìn)程經(jīng)常被視為程序或應(yīng)用的同義詞。然而,用戶所看到的單個(gè)應(yīng)用可能實(shí)際上是一組相互協(xié)作的進(jìn)程。為了便于進(jìn)程之間的通信,大多數(shù)操作系統(tǒng)支持內(nèi)部進(jìn)程通信(Inter Process Communication, IPC),例如管道與套接字。IPC不僅被用于同一個(gè)系統(tǒng)中進(jìn)程之間的通信,還可以處理不同系統(tǒng)中進(jìn)程之間的通信。
              Java虛擬機(jī)的大多數(shù)實(shí)現(xiàn)都是作為單個(gè)進(jìn)程的。一個(gè)Java應(yīng)用程序可以使用ProcessBuilder對(duì)象來(lái)創(chuàng)建額外的進(jìn)程。多進(jìn)程應(yīng)用程序已經(jīng)超出了本課程的范圍。
          線程
              線程有時(shí)候被稱之為輕量級(jí)進(jìn)程。進(jìn)程和線程都能提供一個(gè)執(zhí)行環(huán)境,但創(chuàng)建一個(gè)線程所需要的資源少于創(chuàng)建一個(gè)進(jìn)程。
              線程存在于一個(gè)進(jìn)程中--每個(gè)進(jìn)程至少有一個(gè)線程。線程分享進(jìn)程的資源,包括內(nèi)存和被打開(kāi)的文件。這樣做是為了高效,但在通信方面有著潛在的問(wèn)題。
              多線程執(zhí)行是Java平臺(tái)的一個(gè)本質(zhì)特性。每個(gè)應(yīng)用程序至少擁有一個(gè)線程--或者說(shuō)是多個(gè),如果你把像內(nèi)存管理和信號(hào)處理這樣的系統(tǒng)進(jìn)程算進(jìn)來(lái)的話。但從應(yīng)用程序員的角度來(lái)看,你僅以一個(gè)線程開(kāi)始,該線程被稱之為主線程。該線程擁有創(chuàng)建其它線程的能力,我們將在下一節(jié)中使用例子證明這一點(diǎn)。
          線程對(duì)象
          每個(gè)線程都關(guān)聯(lián)著一個(gè)Thread類的實(shí)例。為了創(chuàng)建并發(fā)應(yīng)用,有兩種使用Thread對(duì)象的基本策略。
          * 為了直接的控制線程的創(chuàng)建與管理,當(dāng)應(yīng)用每次需要啟動(dòng)一個(gè)同步任務(wù)時(shí)就實(shí)例化一個(gè)Thread類。
          * 從你的應(yīng)用中將線程管理抽象出來(lái),將應(yīng)用的任務(wù)傳遞給一個(gè)執(zhí)行器(Executor)。
          本節(jié)將描述Thread對(duì)象的使用,執(zhí)行器將與其它的高級(jí)并發(fā)對(duì)象們一起討論。
          定義與啟動(dòng)一個(gè)線程
          一個(gè)應(yīng)用要?jiǎng)?chuàng)建一個(gè)Thread的實(shí)例就必須提供那些要在該線程中運(yùn)行的代碼。有兩種方法去做這些:
          * 提供一個(gè)Runnable對(duì)象。Runnable接口只定義了一個(gè)方法--run,它要包含那些將在該線程中執(zhí)行的代碼。Runnable對(duì)象將被傳入Thread的構(gòu)造器,如例子HelloRunnable如示:
          public class HelloRunnable implements Runnable {
              public void run() {
                  System.out.println("Hello from a thread!");
              }
              public static void main(String args[]) {
                  (new Thread(new HelloRunnable())).start();
              }
          }
          * 繼承Thread類。Thread類本身也實(shí)現(xiàn)了Runnable接口,可是它的run方法什么都沒(méi)有做。一個(gè)應(yīng)用可以繼承Thread類,并提供它自己的run方法實(shí)現(xiàn),就如HelloThread類所做的:
          public class HelloThread extends Thread {
              public void run() {
                  System.out.println("Hello from a thread!");
              }
              public static void main(String args[]) {
                  (new HelloThread()).start();
              }
          }
              注意,為了啟動(dòng)新的線程,這兩個(gè)例子都調(diào)用了Thread.start方法。
              你應(yīng)該使用哪一種方式呢?第一種方式使用了Runnable對(duì)象,這更加通用,因?yàn)镽unnable對(duì)象還可以繼承Thread之外的其它類。第二種方式可以很方便的使用在簡(jiǎn)單應(yīng)用中,但這樣的話,你的任務(wù)類必須被限制為Thread的一個(gè)子類。本課程關(guān)注于第一種方式,該方法將Runnable任務(wù)從執(zhí)行該任務(wù)的Thread對(duì)象中分離出來(lái)。這種方法不僅更富彈性,而且它還適用于后面將要提到的高級(jí)線程管理API中。
              Thread類為線程的管理定義了一組很有有的方法。它們包括一些能夠提供關(guān)于該線程信息的靜態(tài)方法,以及當(dāng)該線程調(diào)用這些方法時(shí)能夠影響到該線程的狀態(tài)。另一些方法則是被管理該線程和Thread對(duì)象的其它的線程調(diào)用。
          使用sleep方法暫停執(zhí)行
              Thread.sleep方法使當(dāng)前運(yùn)行的線程暫停執(zhí)行一段指定的時(shí)間周期。這是使相同應(yīng)用中的其它線程,或是相同計(jì)算機(jī)系統(tǒng)中的其它應(yīng)用能夠獲得處理器時(shí)間的有效方法。sleep方法也被用于"緩步",如接下來(lái)的例子所示,并且等待其它負(fù)有任務(wù)的線程,這些線程被認(rèn)為有時(shí)間需求。
              sleep方法有兩個(gè)相互重載的版本:一個(gè)指定了睡眠時(shí)間的毫秒數(shù);另一個(gè)指定了睡眠的納秒數(shù)。然而,這些睡眠時(shí)間都無(wú)法得到精確的保證,因?yàn)槭艿讓硬僮飨到y(tǒng)所提供的機(jī)制的限制。而且,睡眠周期會(huì)由于中斷而被停止,我們將在下面的章節(jié)中看到。在任何情況下,你都不能猜想調(diào)用sleep方法后都能精確地在指定時(shí)期周期內(nèi)暫停線程。
              示例SeelpMessages使用sleep方法在每4秒的間隔內(nèi)打印信息:
              public class SleepMessages {
                  public static void main(String args[]) throws InterruptedException {
                      String importantInfo[] = {
                          "Mares eat oats",
                          "Does eat oats",
                          "Little lambs eat ivy",
                          "A kid will eat ivy too"
                      };

                      for (int i = 0; i < importantInfo.length; i++) {
                          //Pause for 4 seconds
                          Thread.sleep(4000);
                          //Print a message
                          System.out.println(importantInfo[i]);
                      }
                  }
              }
              注意main方法聲明了它會(huì)拋出InterruptedException異常。當(dāng)其它的線程中斷了正處于睡眠的當(dāng)前線程時(shí),sleep方法就會(huì)拋出該異常。由于該應(yīng)用并沒(méi)有定義其它的線程去造成中斷,所以它沒(méi)必要去捕獲InterruptedException。
          中斷
              中斷指明了一個(gè)線程應(yīng)該停止它正在做的事情并做些其它的事情。它使程序員要仔細(xì)地考慮讓一個(gè)線程如何回應(yīng)中斷,但十分普通的做法是讓這個(gè)線程停終止。這是本課程特別強(qiáng)調(diào)的用法。
              一個(gè)線程以調(diào)用Thread對(duì)象中的interrupt方法的方式將中斷信號(hào)傳遞給另一個(gè)線程而使它中斷。為了使中斷機(jī)制能正確地工作,被中斷的線程必須支持它自己的中斷。
          支持中斷
              一個(gè)線程如何支持它自己的中斷呢?這取決于它當(dāng)前正在干什么?如果該線程正在調(diào)用那些會(huì)拋出InterruptedException的方法,那么當(dāng)它捕獲了該異常之后就只會(huì)從run方法內(nèi)返回。例如,假設(shè)SleepMessages示例中打印信息的循環(huán)就在該線程的Runnable對(duì)象的run方法中。再做如下修改,使它能夠中斷:
              for (int i = 0; i < importantInfo.length; i++) {
                  //Pause for 4 seconds
                  try {
                      Thread.sleep(4000);
                  } catch (InterruptedException e) {
                      //We've been interrupted: no more messages.
                      return;
                  }
                      //Print a message
                      System.out.println(importantInfo[i]);
              }
              許多會(huì)拋出InterruptedException異常的方法,如sleep,都被設(shè)計(jì)成當(dāng)它們收到一個(gè)中斷時(shí)就取消它們當(dāng)前的操作并立即返回。
              如果該線程長(zhǎng)時(shí)間運(yùn)行且沒(méi)有調(diào)用會(huì)拋出InterruptedException異常的方法,那又會(huì)怎樣呢?它必須周期性地調(diào)用Thread.interrupted方法,如果收到了一個(gè)中斷,該方法將返回true。例如:
              for (int i = 0; i < inputs.length; i++) {
                  heavyCrunch(inputs[i]);
                  if (Thread.interrupted()) {
                      //We've been interrupted: no more crunching.
                      return;
                  }
              }
              在這個(gè)簡(jiǎn)單的例子中,這些代碼僅是簡(jiǎn)單地測(cè)試該線程是否收到了中斷,如果收到了就退出。在更復(fù)雜的例子中,它可能為了更有意義些而拋出一個(gè)InterruptedException異常:
              if (Thread.interrupted()) {
                  throw new InterruptedException();
              }
          這就能使處理中斷的代碼被集中在catch語(yǔ)句塊中。
          中斷狀態(tài)標(biāo)志
              被認(rèn)為是中斷狀態(tài)的內(nèi)部標(biāo)志用于中斷機(jī)制的實(shí)現(xiàn)。調(diào)用Thread.interrupt方法會(huì)設(shè)置這個(gè)標(biāo)記。當(dāng)一個(gè)線程調(diào)用靜態(tài)方法Thread.interrupted去檢查中斷時(shí),中斷狀態(tài)就被清理了。用于一個(gè)線程查詢另一個(gè)線程中斷狀態(tài)的非靜態(tài)方法Thread.isInterrupted不會(huì)改變中斷狀態(tài)標(biāo)志。
              按照慣例,任何通過(guò)拋出InterruptedException異常而退出的方法都會(huì)在它退出時(shí)清理中斷狀態(tài)。然而,總存在著這樣的可能性,中斷狀態(tài)會(huì)由于其它線程調(diào)用interrupt方法而立即被再次設(shè)置。
          Joins
              join允許一個(gè)線程等待另一個(gè)線程完成。如果Thread對(duì)象t的線程當(dāng)前正在執(zhí)行,
              t.join();
          上述語(yǔ)句將導(dǎo)致當(dāng)前線程暫停執(zhí)行只到t的線程終止為止。join的一個(gè)重載版本允許程序員指定等待的周期。然而,與sleep方法一樣,join的時(shí)長(zhǎng)依賴于操作系統(tǒng),所以你不應(yīng)該設(shè)想join將準(zhǔn)確地等待你所指定的時(shí)長(zhǎng)。
              像sleep方法一樣,在由于InterruptedException異常而退出時(shí),join方法也要應(yīng)對(duì)中斷。
          SimpleThreads示例
              下面的例子匯集了本節(jié)一些概念。SimpleThreads類由兩個(gè)線程組成。第一個(gè)線程就是每個(gè)Java應(yīng)用都有的主線程。主線程從一個(gè)Runnable對(duì)象,MessageLoop,創(chuàng)建一個(gè)新的線程,并等待它結(jié)束。如果MessageLoop線程花的時(shí)間太長(zhǎng)了,主線程就會(huì)中斷它。
              MessageLoop線程打印出一系列的信息。如果在打印出所以信息之前就被中斷了,MessageLoop線程將會(huì)打印一條信息并退出。
              public class SimpleThreads {

                  //Display a message, preceded by the name of the current thread
                  static void threadMessage(String message) {
                      String threadName = Thread.currentThread().getName();
                      System.out.format("%s: %s%n", threadName, message);
                  }

                  private static class MessageLoop implements Runnable {
                      public void run() {
                          String importantInfo[] = {
                              "Mares eat oats",
                              "Does eat oats",
                              "Little lambs eat ivy",
                              "A kid will eat ivy too"
                          };
                          try {
                              for (int i = 0; i < importantInfo.length; i++) {
                                  //Pause for 4 seconds
                                  Thread.sleep(4000);
                                  //Print a message
                                  threadMessage(importantInfo[i]);
                              }
                          } catch (InterruptedException e) {
                              threadMessage("I wasn't done!");
                          }
                      }
                  }

                  public static void main(String args[]) throws InterruptedException {
                      //Delay, in milliseconds before we interrupt MessageLoop
                      //thread (default one hour).
                      long patience = 1000 * 60 * 60;

                      //If command line argument present, gives patience in seconds.
                      if (args.length > 0) {
                          try {
                              patience = Long.parseLong(args[0]) * 1000;
                          } catch (NumberFormatException e) {
                              System.err.println("Argument must be an integer.");
                              System.exit(1);
                          }

                      }

                      threadMessage("Starting MessageLoop thread");
                      long startTime = System.currentTimeMillis();
                      Thread t = new Thread(new MessageLoop());
                      t.start();

                      threadMessage("Waiting for MessageLoop thread to finish");
                      //loop until MessageLoop thread exits
                      while (t.isAlive()) {
                          threadMessage("Still waiting...");
                          //Wait maximum of 1 second for MessageLoop thread to
                          //finish.
                          t.join(1000);
                          if (((System.currentTimeMillis() - startTime) > patience) &&
                                  t.isAlive()) {
                              threadMessage("Tired of waiting!");
                              t.interrupt();
                              //Shouldn't be long now -- wait indefinitely
                              t.join();
                          }
                      }
                      threadMessage("Finally!");
                  }
              }

          同步
          線程通信主要是通過(guò)訪問(wèn)共享的字段以及這些字段所涉及的對(duì)象引用。這種通信的形式十分的高效,但它可能造成兩種錯(cuò)誤:線程干涉和內(nèi)存一致性錯(cuò)誤。用于阻止這些錯(cuò)誤的工具就是同步。
          * 線程干預(yù)介紹了當(dāng)多個(gè)線程訪問(wèn)共享數(shù)據(jù)時(shí)產(chǎn)生的錯(cuò)誤。
          * 內(nèi)存一致性錯(cuò)誤介紹了指由對(duì)共享內(nèi)存不一致的查看而導(dǎo)致的錯(cuò)誤。
          * 同步方法介紹了一種能夠有效地防止線程干預(yù)和內(nèi)存一致性錯(cuò)誤的常用方法。
          * 隱含鎖和同步介紹了一種更通用的同步方法,并介紹了同步是如何基于隱含鎖的。
          * 原子訪問(wèn)介紹這種通用的不會(huì)受其它線程干預(yù)的操作概念。
          線程干預(yù)
          考慮這個(gè)叫Counter的簡(jiǎn)單類
              class Counter {
                  private int c = 0;

                  public void increment() {
                      c++;
                  }

                  public void decrement() {
                      c--;
                  }

                  public int value() {
                      return c;
                  }
              }

          Counter被設(shè)計(jì)成讓每次調(diào)用increment方法后c就加1,而每次調(diào)用decrement方法后c就減1。然而,如果一個(gè)Counter對(duì)象被多個(gè)線程所引用,那么線程之前的干預(yù)可能不會(huì)使所期望的事情發(fā)生。
          當(dāng)在不同線程中的兩個(gè)操作交叉地作用于同一數(shù)據(jù)時(shí),干預(yù)就發(fā)生了。這就是說(shuō)兩個(gè)操作由多步組成,并且步調(diào)之間相互重疊。
          看起來(lái)作用于Counter實(shí)例的操作不可能是交叉的,因?yàn)檫@兩個(gè)關(guān)于c變量的操作都是一元的簡(jiǎn)單語(yǔ)句。可是,如此簡(jiǎn)單的語(yǔ)句也能夠被虛擬機(jī)解釋成多個(gè)步驟。我們不用檢查虛擬機(jī)所做的特定步驟--我們足以知道一元表達(dá)式c++可被分解成如下三步:
          1. 獲取c的當(dāng)前值。
          2. 將這個(gè)被取出的值加1。
          3. 將被加的值再放回c變量中。
          表達(dá)式c--也能被進(jìn)行相同地分解,除了將第二步的加替換為減。
          猜想在線程B調(diào)用decrement方法時(shí),線程A調(diào)用了increment方法。如果c的初始值為0,它們交叉的動(dòng)作可能是如下順序:
          1. 線程A:取出c。
          2. 線程B:取出c。
          3. 線程A:將取出的值加1,結(jié)果為1。
          4. 線程B:將取出的值減一,結(jié)果為-1。
          5. 線程A:將結(jié)果存于c中,c現(xiàn)在為1。
          6. 線程B:將結(jié)果存于c中,c現(xiàn)在為-1。
          線程A的結(jié)果丟失了,被線程B覆蓋了。這個(gè)特殊的交叉只是一種可能性。在不同的環(huán)境下,可能是線程B的結(jié)果丟失,也可能根本就沒(méi)有發(fā)生任何錯(cuò)誤。因?yàn)樗鼈兪遣豢赡茴A(yù)知的,所以很難發(fā)現(xiàn)并修正線程干預(yù)缺陷。

          內(nèi)存一致性錯(cuò)誤
          當(dāng)不同的線程觀察到本應(yīng)相同但實(shí)際上不同的數(shù)據(jù)時(shí),內(nèi)存一致性錯(cuò)誤就發(fā)生了。導(dǎo)致內(nèi)存一致性錯(cuò)誤的原因十分復(fù)雜并且超出了本教程的范圍。幸運(yùn)地是,應(yīng)用程序員并不需要了解這些原因的細(xì)節(jié)。所需要的就是一個(gè)避免它們的策略。
          避免內(nèi)存一致性錯(cuò)誤的關(guān)鍵就是要理解"happens-before"的關(guān)系。這個(gè)關(guān)系保證了由一個(gè)特定的語(yǔ)句所寫的內(nèi)存對(duì)其它特定的語(yǔ)句都是可見(jiàn)。為了解它,可以考慮下面的例子。假設(shè)一個(gè)簡(jiǎn)單的int型字段的定義與初始化:
          int counter = 0;
          counter字段被兩個(gè)線程,A和B,共享。假設(shè)線程A增加counter的值:
          counter++;
          很短的時(shí)間之后,線程B打印出counter的值:
          System.out.println(counter);
          如果這兩條語(yǔ)句是在同一個(gè)線程中執(zhí)行,那就是可以很肯定地猜測(cè)被打印出的會(huì)是"1"。但如果在不同的線程中執(zhí)行這兩條語(yǔ)句,被打印出的值可能正好是"0",因?yàn)闆](méi)有什么能保證線程A對(duì)counter的改變能被線程B看到--除非應(yīng)用程序員在這兩條語(yǔ)句之間建立了"happens-before"關(guān)系。
          有多種方式能夠創(chuàng)建"happens-before"關(guān)系。其中之一就是同步,我們將在接下來(lái)的一節(jié)中看到它。
          我們已經(jīng)看到了兩種建立"happens-before"關(guān)系的方法。
          * 當(dāng)一條語(yǔ)句調(diào)用Thread.start方法,一個(gè)新的線程執(zhí)行的每條語(yǔ)句都有"happens-before"關(guān)系的語(yǔ)句與那些也有著"happens-before"關(guān)系。這些代碼的作用就是使新線程的創(chuàng)建對(duì)于其它的新線程是可見(jiàn)的。
          * 當(dāng)一個(gè)線程終止并在另一個(gè)線程中調(diào)用Thread.join導(dǎo)致返回,然后所有的由已終止的線程執(zhí)行的語(yǔ)句伴著隨后成功join的所有語(yǔ)句都有"happens-before"關(guān)系。那么在該線程中的代碼所產(chǎn)生的影響對(duì)于join進(jìn)來(lái)的線程就是可見(jiàn)的。
          要看創(chuàng)建"happens-before"關(guān)系的一列方法,可以參考java.util.concurrent包的摘要頁(yè)面。

          同步方法
          Java設(shè)計(jì)程序需要提供兩種基本的同步常用法:同步方法和同步語(yǔ)句。其中更為復(fù)雜的一種,同步語(yǔ)句,將在下一節(jié)講述。本節(jié)是關(guān)于同步方法的。
          使一個(gè)方法是可同步的,只要簡(jiǎn)單地將關(guān)鍵字synchronized加到它的聲明中:
              public class SynchronizedCounter {
                  private int c = 0;

                  public synchronized void increment() {
                      c++;
                  }

                  public synchronized void decrement() {
                      c--;
                  }

                  public synchronized int value() {
                      return c;
                  }
              }

          如果count是SynchronizedCounter的一個(gè)實(shí)例,那么使這些方法同步將有兩個(gè)作用:
          * 第一,對(duì)同一個(gè)對(duì)象中的同步方法進(jìn)行交叉的調(diào)用就不可能了。當(dāng)一個(gè)線程正在調(diào)用一個(gè)對(duì)象中的一個(gè)同步方法時(shí),所有其它的調(diào)用該對(duì)象的同步方法的線程將被阻塞,直到第一個(gè)線程結(jié)束對(duì)該對(duì)象的工作。
          * 第二,當(dāng)同步方法存在,它就會(huì)與在同一對(duì)象中后序調(diào)用的方法自動(dòng)地建立"happens-before"關(guān)系。
          注意,構(gòu)造器不能是可同步--對(duì)一個(gè)構(gòu)造器使用關(guān)鍵字synchronized是一個(gè)語(yǔ)法錯(cuò)誤。同步構(gòu)造器沒(méi)有意義,因?yàn)橹挥幸粋€(gè)線程要?jiǎng)?chuàng)建對(duì)象,當(dāng)它正在被構(gòu)造時(shí)才會(huì)訪問(wèn)構(gòu)造器。
          警告:當(dāng)構(gòu)造一個(gè)將會(huì)在線程之間共享的對(duì)象時(shí),要非常小心對(duì)象的引用過(guò)早地"溢出"。例如,假設(shè)你要維護(hù)一個(gè)叫instances的List去包含class的每個(gè)實(shí)例。你可能會(huì)嘗試著加入下面一行
          instances.add(this);
          到你的構(gòu)造囂。但之后其它的線程可以在這個(gè)對(duì)象構(gòu)造完成之前就可以instances去訪問(wèn)該對(duì)象。
          同步方法使一個(gè)簡(jiǎn)單的防止線程干預(yù)和內(nèi)存一致錯(cuò)誤的策略成為可能:如果一個(gè)對(duì)象對(duì)于一個(gè)以上的線程是可見(jiàn)的,所有針對(duì)該對(duì)象的變量的讀與寫都要通過(guò)同步方法。(有一個(gè)很重要的例外:在被構(gòu)造之后就不能被修改的final字段,一旦它被創(chuàng)建,就能夠被非同步方法安全地讀取)。這種策略十分高效,但會(huì)出現(xiàn)活躍度問(wèn)題,我們將在后面的教程中見(jiàn)到。
          內(nèi)部鎖與同步
          同步是圍繞著一個(gè)被認(rèn)為是內(nèi)部鎖或監(jiān)視鎖的內(nèi)部實(shí)體而建立(API規(guī)范經(jīng)常就稱這個(gè)實(shí)體為"監(jiān)視器")。內(nèi)部鎖在同步的兩個(gè)方面扮演著角色:強(qiáng)制排他地訪問(wèn)一個(gè)對(duì)象的狀態(tài),為那些必須是可見(jiàn)的狀態(tài)建立"happen-before"關(guān)系。
          每個(gè)對(duì)象都有一個(gè)與之關(guān)聯(lián)的內(nèi)部鎖。一般地,一個(gè)線程要排他并一致地訪問(wèn)一個(gè)對(duì)象的字段就必須在訪問(wèn)它之前就獲得這個(gè)對(duì)象的內(nèi)部鎖,在這個(gè)線程使用完之后就釋放這個(gè)內(nèi)部鎖。在獲得鎖與釋放鎖之間的這段時(shí)間內(nèi),這個(gè)線程被認(rèn)為擁有這個(gè)內(nèi)部鎖。一但線程擁有了內(nèi)部鎖,其它的線程就不能再獲得相同的鎖了。當(dāng)另一個(gè)線程試圖獲得這個(gè)鎖時(shí),它將會(huì)被阻塞。
          當(dāng)線程釋放了一個(gè)內(nèi)部鎖,在這個(gè)動(dòng)作與后續(xù)想獲得同一個(gè)鎖的動(dòng)作之間的"happens-before"關(guān)系就建立起來(lái)了。
          在同步方法中的鎖
          當(dāng)線程調(diào)用了同步方法,它就自動(dòng)地獲得這個(gè)方法所在對(duì)象的內(nèi)部鎖,當(dāng)這個(gè)方法返回時(shí)它就會(huì)釋放這個(gè)鎖。即使這個(gè)返回是由一個(gè)未捕獲的異常造成的,鎖也會(huì)被釋放。
          你可能會(huì)對(duì)調(diào)用一個(gè)靜態(tài)的同步方法時(shí)所發(fā)生的事情感到驚訝,因?yàn)殪o態(tài)方法是與一個(gè)類,而不是一個(gè)對(duì)象,相關(guān)聯(lián)的。在這種情況下,線程要求獲得與這個(gè)類相關(guān)的Class對(duì)象的內(nèi)部鎖。因此訪問(wèn)類的靜態(tài)字段是被一個(gè)與作用于類的實(shí)例的鎖不同的鎖控制的。
          同步語(yǔ)句
          創(chuàng)建同步代碼的另一種方式是使用同步語(yǔ)句。與同步方法不同,同步語(yǔ)句必須要指定提供內(nèi)部鎖的對(duì)象:
              public void addName(String name) {
                  synchronized(this) {
                      lastName = name;
                      nameCount++;
                  }
                  nameList.add(name);
              }

          在這個(gè)例子中,addName方法要對(duì)lastName和nameCount的修改進(jìn)行同步,但也要避免同步地調(diào)用另一個(gè)對(duì)象中的方法(從同步代碼中調(diào)用另一個(gè)對(duì)象的方法會(huì)產(chǎn)生的問(wèn)題將在Liveness章節(jié)中講述)。不用同步語(yǔ)句,就只能是一個(gè)隔離的非同步方法,其目的只是為了調(diào)用nameList.add方法。
          同步語(yǔ)句對(duì)使用細(xì)致的同步去提高并發(fā)應(yīng)用也是有用的。例如,假設(shè)類MsLunch有兩個(gè)實(shí)例字段,c1和c2,從來(lái)都沒(méi)有一起被使用過(guò)。這些字段的更新都必須是同步的,但沒(méi)有道理在交叉地對(duì)c2進(jìn)行更新時(shí)防止對(duì)c1的更新--這樣做會(huì)創(chuàng)建不必要的阻塞而減少并發(fā)。我們創(chuàng)建兩個(gè)對(duì)象單獨(dú)地提供鎖,而不是使用同步方法或反而使用與this關(guān)聯(lián)的鎖。
              public class MsLunch {
                  private long c1 = 0;
                  private long c2 = 0;
                  private Object lock1 = new Object();
                  private Object lock2 = new Object();

                  public void inc1() {
                      synchronized(lock1) {
                          c1++;
                      }
                  }

                  public void inc2() {
                      synchronized(lock2) {
                          c2++;
                      }
                  }
              }

          使用這種方法必須極其的小心。你必須非常地肯定交叉地訪問(wèn)這些受影響的字段是安全的。
          可重進(jìn)入的同步
          回憶一下,線程不能獲得被其它線程占有的鎖。但線程可以獲得被它自己占有的鎖。允許線程多次獲得相同的鎖就能夠形成可重進(jìn)入的同步。這就能解釋這樣一種情況,當(dāng)同步代碼直接或間接地調(diào)用了一個(gè)已經(jīng)包含同步代碼的方法,但兩組代碼都使用相同的鎖。沒(méi)有可重進(jìn)入的同步,同步代碼將不得不采取更多額外的預(yù)防措施去避免線程被自己阻塞。
          原子訪問(wèn)
          在編程中,一個(gè)原子操作就是所有有效的動(dòng)作一次性發(fā)生。原子操作不能在中間停止:它要么完全發(fā)生,要么完全不發(fā)生。原子操作不會(huì)有任何可見(jiàn)的副作用,直到該行為完成。
          我們已經(jīng)看到了像c++這樣的加法表達(dá)式不是一個(gè)原子操作。非常簡(jiǎn)單的表達(dá)甚至都可以被定義成能被分解為其它操作的復(fù)雜操作。但是,有些操作你可以認(rèn)為它們是原子的:
          * 讀和寫引用變量和大多數(shù)基本數(shù)據(jù)類型變量(除long和double之外的其它基本數(shù)據(jù)類型)
          * 讀和寫被聲明為volatile的變量(包括long和double型的變量)都是原子的。
          原子操作不能被交叉地執(zhí)行,因此使用它們可以不必?fù)?dān)心線程干預(yù)。然而,這并不能完全清除對(duì)原子操作進(jìn)行同步的需要,因?yàn)閮?nèi)存一致性錯(cuò)誤的可能性仍然存在。使用volatile變量可以降低內(nèi)存一致性錯(cuò)誤的風(fēng)險(xiǎn),因?yàn)槿魏吾槍?duì)volatile變量的寫操作都與后續(xù)的針對(duì)該變量的讀操作之間建立了"happen-before"關(guān)系。這就意味著對(duì)一個(gè)voloatile變量的改變對(duì)于其它線程都是可見(jiàn)的。進(jìn)一步說(shuō),這也意味著當(dāng)一個(gè)線程讀一個(gè)volatile變量時(shí),它不僅能看到該volatile變量的最新變化,也能看到導(dǎo)致該變化的代碼的副作用。
          使簡(jiǎn)潔的原子變量訪問(wèn)比通過(guò)同步代碼訪問(wèn)這些變量更加高效,但也要求應(yīng)用程序員更加小心以避免內(nèi)存一致性錯(cuò)誤。額外的努力是否值得,取決于應(yīng)用的規(guī)模與復(fù)雜度。
          java.util.concurrent包中的一些類提供了一些不依賴于同步的原子方法。我們將在High Level Concurrency Objects一節(jié)中討論它們。
          死鎖
              死鎖描述了一種兩個(gè)或以上的線程永久地相互等待而被阻塞的情形。這兒就有一個(gè)例子。
              Alphonse和Gaston是朋友,并且都很崇尚禮節(jié)。禮節(jié)的一條嚴(yán)格規(guī)則就是,當(dāng)你向朋友鞠躬時(shí),你必須保持鞠躬的姿勢(shì)直到你的朋友能有機(jī)會(huì)向你還以鞠躬。不幸地是,這條規(guī)則沒(méi)有說(shuō)明這樣一種可能性,即兩個(gè)朋友可能在同時(shí)間相互鞠躬。
              public class Deadlock {
                  static class Friend {
                      private final String name;
                          public Friend(String name) {
                              this.name = name;
                          }
                          public String getName() {
                              return this.name;
                          }
                          public synchronized void bow(Friend bower) {
                              System.out.format("%s: %s has bowed to me!%n",
                                      this.name, bower.getName());
                              bower.bowBack(this);
                          }
                          public synchronized void bowBack(Friend bower) {
                              System.out.format("%s: %s has bowed back to me!%n",
                                      this.name, bower.getName());
                          }
                      }

                  public static void main(String[] args) {
                      final Friend alphonse = new Friend("Alphonse");
                      final Friend gaston = new Friend("Gaston");
                      new Thread(new Runnable() {
                          public void run() { alphonse.bow(gaston); }
                      }).start();
                      new Thread(new Runnable() {
                          public void run() { gaston.bow(alphonse); }
                      }).start();
                  }
              }
              當(dāng)Deadlock運(yùn)行后,極有可能當(dāng)兩個(gè)線程試圖調(diào)用bowBack時(shí)它們被阻塞了。沒(méi)有一種阻塞會(huì)結(jié)束,因?yàn)槊總€(gè)線程都在另一方退出bow方法。
          饑餓與活性鎖
              饑餓與活性鎖是沒(méi)有死鎖那么普遍的問(wèn)題,也仍然是每個(gè)并發(fā)軟件的設(shè)計(jì)者都可能遇到的問(wèn)題。
              饑餓
              饑餓所描述的情形是指當(dāng)一個(gè)線程不能正常地訪問(wèn)到共享資源,也就不能得到進(jìn)步。當(dāng)共享資源被"貪婪"的線程長(zhǎng)時(shí)間占有時(shí),這種情況就會(huì)發(fā)生。例如,假設(shè)一個(gè)對(duì)象提供了一個(gè)經(jīng)常會(huì)耗費(fèi)很長(zhǎng)時(shí)間才會(huì)返回的同步方法。如果一個(gè)線程頻繁地調(diào)用這個(gè)方法,其它也需要頻繁地同步訪問(wèn)相同對(duì)象的線程就會(huì)經(jīng)常被阻塞。
              活性鎖
              一個(gè)線程的行為經(jīng)常是對(duì)另一個(gè)線程的行為的響應(yīng)。如果另一個(gè)線程的行為也是對(duì)另一個(gè)線程的行為的響應(yīng),這時(shí)活性鎖可能就產(chǎn)生了。與死鎖比較,活性鎖線程是不能得到更進(jìn)一步的進(jìn)步。但是這些線程并沒(méi)有被阻塞--它們只是過(guò)分疲于應(yīng)付彼此而不能恢復(fù)工作。就能比喻成在走廊中的兩個(gè)人都試圖通過(guò)對(duì)方:Alphonse向他的左邊移動(dòng)以讓Gaston通過(guò),此時(shí)Gaston則向他的右邊移動(dòng)以讓Alphonse通過(guò)。看到他們?nèi)匀槐舜俗枞珹lphone就向他的右邊移動(dòng),此時(shí)Gaston向他的左邊移動(dòng)。這樣,他們?nèi)匀桓髯宰枞鴮?duì)方...
          受保護(hù)的塊
              線程經(jīng)常不得不調(diào)整它們的行為。最常用的調(diào)整方式就是受保護(hù)的塊。在執(zhí)行之前,這種塊開(kāi)始時(shí)會(huì)輪詢查檢某個(gè)條件必須為成立。為了正確地做到這一點(diǎn)有許多步驟需要遵守。
              例如,假設(shè)guardedJoy方法將不會(huì)執(zhí)行,直到共享變量joy被別的線程設(shè)置過(guò)。理論上,這樣的一個(gè)方法可以不停的循環(huán)直到條件滿足為止。但這個(gè)循環(huán)不經(jīng)濟(jì),因?yàn)樵诘却臅r(shí)候它仍然在持續(xù)不停的運(yùn)行。
              public void guardedJoy() {
                  //Simple loop guard. Wastes processor time. Don't do this!
                  while(!joy) {}
                  System.out.println("Joy has been achieved!");
              }
              一種更高效的保護(hù)方式就是調(diào)用Object.wait方法去暫停當(dāng)前的線程。調(diào)用wait方法不會(huì)返回直到另一個(gè)線程發(fā)出通知,說(shuō)某個(gè)特定事件已經(jīng)發(fā)生過(guò)了--盡管這個(gè)線程所等待的事件并不是必要的:
              public synchronized guardedJoy() {
                  //This guard only loops once for each special event, which may not
                  //be the event we're waiting for.
                  while(!joy) {
                      try {
                          wait();
                      } catch (InterruptedException e) {}
                  }
                  System.out.println("Joy and efficiency have been achieved!");
              }
              注意:總是在循環(huán)內(nèi)部調(diào)用wait方法去測(cè)試所等待的條件是否成立。不要猜想你所等待著的特殊條件中斷了,或者這個(gè)條件仍然成立。
              就像許多延緩執(zhí)行的方法一樣,wait也會(huì)拋出InterruptedException異常。在這個(gè)例子中,我們可以忽略這個(gè)異常--我們僅關(guān)注joy的值。
              為什么guardedJoy的這個(gè)版本是可同步的?假設(shè)我們用調(diào)用wait方法的對(duì)象是d,當(dāng)一個(gè)線程調(diào)用了wait方法,它必須擁有對(duì)象d的內(nèi)部鎖--否則一個(gè)錯(cuò)誤就會(huì)發(fā)生。在一個(gè)同步方法內(nèi)部調(diào)用wait方法是一種獲得內(nèi)部鎖的簡(jiǎn)便途徑。
              當(dāng)wait方法被調(diào)用了,該線程就釋放鎖并掛起執(zhí)行。在以后的某個(gè)時(shí)間,另一個(gè)線程將會(huì)獲得相同的鎖并調(diào)用Object.notifyALL方法,通知所有正在等待這個(gè)鎖的線程某個(gè)重要的事情已經(jīng)發(fā)生過(guò)了:
              public synchronized notifyJoy() {
                  joy = true;
                  notifyAll();
              }
              在第二個(gè)線程已經(jīng)釋放鎖之后的某個(gè)時(shí)間,第一個(gè)線程重新獲得鎖并從wait方法的調(diào)用中返回以恢復(fù)執(zhí)行。
              注意:還有另一個(gè)通知方法,notify,該方法只喚醒一個(gè)線程。因?yàn)閚otify方法不允許你指定被喚醒的線程,所以它只用于大并發(fā)應(yīng)用程序中--即,這個(gè)程序擁有大量的線程,這些線程又都做類似的事情。在這樣的應(yīng)用中,你并不關(guān)心是哪個(gè)線程被喚醒了。
              讓我們使用受保護(hù)的塊去創(chuàng)建生產(chǎn)者-消費(fèi)者應(yīng)用。該種應(yīng)用是在兩個(gè)線程中共享數(shù)據(jù):生產(chǎn)者創(chuàng)建數(shù)據(jù),而消費(fèi)者使用數(shù)據(jù)。這兩個(gè)線程使用一個(gè)共享對(duì)象進(jìn)行通信。協(xié)調(diào)是必須的:消費(fèi)者線程在生產(chǎn)者線程交付這個(gè)數(shù)據(jù)之前不能試圖去獲取它,在消費(fèi)者還沒(méi)有獲取老的數(shù)據(jù)之前生產(chǎn)者不能試圖交付新的數(shù)據(jù)。
              在這個(gè)例子中,數(shù)據(jù)是一系列的文本信息,它們通過(guò)類型為Drop的對(duì)象進(jìn)行共享:
              public class Drop {
                  //Message sent from producer to consumer.
                  private String message;
                  //True if consumer should wait for producer to send message, false
                  //if producer should wait for consumer to retrieve message.
                  private boolean empty = true;

                  public synchronized String take() {
                      //Wait until message is available.
                      while (empty) {
                          try {
                              wait();
                          } catch (InterruptedException e) {}
                      }
                      //Toggle status.
                      empty = true;
                      //Notify producer that status has changed.
                      notifyAll();
                      return message;
                  }

                  public synchronized void put(String message) {
                      //Wait until message has been retrieved.
                      while (!empty) {
                          try {
                              wait();
                          } catch (InterruptedException e) {}
                      }
                      //Toggle status.
                      empty = false;
                      //Store message.
                      this.message = message;
                      //Notify consumer that status has changed.
                      notifyAll();
                  }
              }

              生產(chǎn)者進(jìn)程,由Producer類定義,傳遞一系列類似的信息。字符串"DONE"表示所有的信息都已經(jīng)發(fā)出了。為了模擬真實(shí)應(yīng)用的不可能預(yù)知性,生產(chǎn)者線程在兩次發(fā)送信息之間會(huì)暫停一個(gè)隨機(jī)的時(shí)間間隔。

              import java.util.Random;

              public class Producer implements Runnable {
                  private Drop drop;

                  public Producer(Drop drop) {
                      this.drop = drop;
                  }

                  public void run() {
                      String importantInfo[] = {
                          "Mares eat oats",
                          "Does eat oats",
                          "Little lambs eat ivy",
                          "A kid will eat ivy too"
                      };
                      Random random = new Random();

                      for (int i = 0; i < importantInfo.length; i++) {
                          drop.put(importantInfo[i]);
                          try {
                              Thread.sleep(random.nextInt(5000));
                          } catch (InterruptedException e) {}
                      }
                      drop.put("DONE");
                  }
              }
              消費(fèi)者線程,由Consumer類定義,就獲得信息并把它們打印出來(lái),直到獲得"DONE"對(duì)象為止。該線程也會(huì)在隨機(jī)的時(shí)間間隔內(nèi)暫停執(zhí)行。

              import java.util.Random;
              
              public class Consumer implements Runnable {
                  private Drop drop;

                  public Consumer(Drop drop) {
                      this.drop = drop;
                  }

                  public void run() {
                      Random random = new Random();
                      for (String message = drop.take(); ! message.equals("DONE");
                          message = drop.take()) {
                          System.out.format("MESSAGE RECEIVED: %s%n", message);
                          try {
                              Thread.sleep(random.nextInt(5000));
                          } catch (InterruptedException e) {}
                      }
                  }
              }

              最后就是main線程了,定義在了ProducerConsumerExample類中,該類將啟動(dòng)生產(chǎn)者和消費(fèi)者線程。

              public class ProducerConsumerExample {
                  public static void main(String[] args) {
                      Drop drop = new Drop();
                      (new Thread(new Producer(drop))).start();
                      (new Thread(new Consumer(drop))).start();
                  }
              }

              注意:Drop類是為了證明受保護(hù)的塊而寫的。為了避免重新發(fā)明輪子,在嘗試測(cè)試你自己的數(shù)據(jù)共享對(duì)象之前可以先使用Java集合框架中的數(shù)據(jù)結(jié)構(gòu)。
          不可變對(duì)象
              如果一個(gè)對(duì)象的狀態(tài)在它被創(chuàng)建之后就不能修改了,這樣的對(duì)象就被認(rèn)為是不可變的。最大程度地依賴不可變對(duì)象是一個(gè)被廣泛接受的用來(lái)創(chuàng)建簡(jiǎn)潔而可靠代碼的良好策略。
              不可變對(duì)象在并發(fā)應(yīng)用中特別有用。由于它們的狀態(tài)不能改變,它們就不會(huì)有線程干預(yù)的困擾,也不會(huì)被觀察到不一致的狀態(tài)。
              應(yīng)用程序員經(jīng)常不使用不可變對(duì)象,因?yàn)樗麄儞?dān)心創(chuàng)建一個(gè)新的對(duì)象而不是更新已有對(duì)象的狀態(tài)所付出的代價(jià)。創(chuàng)建對(duì)象的代價(jià)經(jīng)常被高估了,而且與不可變對(duì)象相關(guān)的高效率也可抵消一些新建對(duì)象的代價(jià)。
              后面的子章節(jié)將使用一個(gè)使用可變實(shí)例的類,然后再?gòu)倪@個(gè)類派生出一個(gè)使用不可變實(shí)例的類。通過(guò)所做的這些,它們給出了一個(gè)進(jìn)行這種轉(zhuǎn)變的通用規(guī)則,并證明了不可變對(duì)象的一些好處。
          一個(gè)同步類的例子
              類SynchronizedRGB定義的對(duì)象用于表示色彩。每個(gè)對(duì)象用代表主色值的三個(gè)整數(shù)去表示色彩,并用一個(gè)字符串表示這種色彩的名稱。
              public class SynchronizedRGB {
                  //Values must be between 0 and 255.
                  private int red;
                  private int green;
                  private int blue;
                  private String name;

              private void check(int red, int green, int blue) {
                      if (red < 0 || red > 255
                              || green < 0 || green > 255
                              || blue < 0 || blue > 255) {
                          throw new IllegalArgumentException();
                      }
                  }

                  public SynchronizedRGB(int red, int green, int blue, String name) {
                      check(red, green, blue);
                      this.red = red;
                      this.green = green;
                      this.blue = blue;
                      this.name = name;
                  }

                  public void set(int red, int green, int blue, String name) {
                      check(red, green, blue);
                      synchronized (this) {
                          this.red = red;
                          this.green = green;
                          this.blue = blue;
                          this.name = name;
                      }
                  }

                  public synchronized int getRGB() {
                      return ((red << 16) | (green << 8) | blue);
                  }

                  public synchronized String getName() {
                      return name;
                  }

                  public synchronized void invert() {
                      red = 255 - red;
                      green = 255 - green;
                      blue = 255 - blue;
                      name = "Inverse of " + name;
                  }
              }
              SynchronizedRGB必須小心地避免被觀察到不一致的狀態(tài)。例如,假設(shè)一個(gè)線程執(zhí)行下面的代碼:
              SynchronizedRGB color = new SynchronizedRGB(0, 0, 0, "Pitch Black");
                  ...
                  int myColorInt = color.getRGB();      //Statement 1
                  String myColorName = color.getName(); //Statement 2
              如果另一個(gè)線程在Statement 1之后而在Statement 2之前調(diào)用了color.set方法,那么myColorInt的值就不匹配myColorName表示的色彩。為了避免這種結(jié)果,這兩條語(yǔ)句必須綁在一起:
              synchronized (color) {
                  int myColorInt = color.getRGB();
                  String myColorName = color.getName();
              }
              這種不一致性只可能發(fā)生在不可變對(duì)象上--對(duì)于不可變版本的SynchronizedRGB就不會(huì)有這種問(wèn)題。
          定義不可變對(duì)象的策略
              下面的規(guī)則定義了一種簡(jiǎn)單的創(chuàng)建不可變對(duì)象的策略。不是所有被標(biāo)為"immutable"的類都符合下面的這些規(guī)則。這也不是說(shuō)這些類的創(chuàng)建者缺乏考慮--他們可能有好的理由去相信他們的類的實(shí)例在構(gòu)造完畢之后就不會(huì)再改變了。
              1. 不要提供"setter"方法-修改字段的方法或由這些字段引用的對(duì)象。
              2. 將所有的字段設(shè)置為final和private。
              3. 不允許子類去重載方法。實(shí)現(xiàn)這一點(diǎn)的最簡(jiǎn)單的路徑就是將該類聲明為final。更復(fù)雜一點(diǎn)兒的方法是聲明該類的構(gòu)造器為private,并通過(guò)工廠方法創(chuàng)建實(shí)例。
              4. 如果實(shí)例字段包含對(duì)可變對(duì)象的引用,就不允許這些對(duì)象被改變:
                  * Don't provide methods that modify the mutable objects.
                  * 不要提供修改這些對(duì)象的方法。
                  * Don't share references to the mutable objects. Never store references to external, mutable objects passed to the constructor; if necessary, create copies, and store references to the copies. Similarly, create copies of your internal mutable objects when necessary to avoid returning the originals in your methods.
                  * 不要共享對(duì)可變對(duì)象的引用。不要通過(guò)構(gòu)造器存儲(chǔ)對(duì)外部可變對(duì)象的引用;如果必須那么做,就創(chuàng)建一個(gè)拷貝,將引用存放到拷貝中。類似的,如果有必要避免在你的方法內(nèi)部返回原始對(duì)象,可以為你的內(nèi)部可變對(duì)象創(chuàng)建拷貝。
              將該策略應(yīng)用到SynchronizedRGB中就會(huì)產(chǎn)生如下步驟:
              1. 該類中有兩個(gè)setter方法。首先是set方法,無(wú)論使用何種方式改變?cè)搶?duì)象,在該類的不可變版本中都不可能再有它的位置了。其次就是invert方法,可以使用它創(chuàng)建一個(gè)新的對(duì)象而不是修改已有的對(duì)象。
              2. 所有的字段都已經(jīng)是私有的了;再進(jìn)一步使它們是final的。
              3. 將該類本身聲明為final。
              4. 僅有一個(gè)引用其它對(duì)象的字段,而那個(gè)對(duì)象本身也是不可變的。因此,針對(duì)包含可變對(duì)象的狀態(tài)的防護(hù)手段都是不必要的了。
              做完這些之后,我們就有了ImmutableRGB:
              final public class ImmutableRGB {
                  //Values must be between 0 and 255.
                  final private int red;
                  final private int green;
                  final private int blue;
                  final private String name;

                  private void check(int red, int green, int blue) {
                      if (red < 0 || red > 255
                              || green < 0 || green > 255
                              || blue < 0 || blue > 255) {
                          throw new IllegalArgumentException();
                      }
                  }

                  public ImmutableRGB(int red, int green, int blue, String name) {
                      check(red, green, blue);
                      this.red = red;
                      this.green = green;
                      this.blue = blue;
                      this.name = name;
                  }

                  public int getRGB() {
                      return ((red << 16) | (green << 8) | blue);
                  }

                  public String getName() {
                      return name;
                  }

                  public ImmutableRGB invert() {
                      return new ImmutableRGB(255 - red, 255 - green, 255 - blue,
                          "Inverse of " + name);
                  }
              }
          高層次并發(fā)對(duì)象
              到目前為止,本教程已經(jīng)關(guān)注了在一開(kāi)始就是Java平臺(tái)一部分的低層次API。這些API足以應(yīng)付非常基本的工作,但對(duì)于更高級(jí)的工作,高層次組件是必需的。對(duì)于需要充分發(fā)掘當(dāng)今多多處理器和多核系統(tǒng)的大規(guī)模并發(fā)應(yīng)用就更是如此。
              在本節(jié),我們將看到一些在Java平臺(tái)5.0版中引入的高級(jí)并發(fā)特性。這些特性中的大部分是在java.util.concurrent包中實(shí)現(xiàn)的。在Java集合框架中也有一些新的并發(fā)數(shù)據(jù)結(jié)構(gòu)。
                  * 支持鎖機(jī)制的鎖對(duì)象簡(jiǎn)化了很多并發(fā)應(yīng)用。
                  * 執(zhí)行器為啟動(dòng)和管理線程定義了一個(gè)高級(jí)API。由java.util.concurrent包提供的執(zhí)行器的實(shí)現(xiàn)提供了適應(yīng)于大規(guī)模應(yīng)用的線程池管理。
                  * 并發(fā)集合使得管理大型數(shù)據(jù)集合更為簡(jiǎn)易,并能大幅減少去同步的需求。
                  * 原子變量擁有最小化對(duì)同步的需求并幫助避免內(nèi)存一致性錯(cuò)誤的特性。
          鎖對(duì)象
              同步代碼依賴于一種簡(jiǎn)單的可重入鎖。這種鎖方便使用,但有很多限制。java.util.concurrent.locks包提供了更為復(fù)雜的鎖機(jī)制。我們不會(huì)細(xì)致地測(cè)試這個(gè)包,而是關(guān)注它最基本的接口,Lock。
              鎖對(duì)象工作起來(lái)非常像由同步代碼使用的隱含鎖。使用隱含鎖時(shí),在一個(gè)時(shí)間點(diǎn)只有一條線程能夠擁有鎖對(duì)象。通過(guò)與之相關(guān)聯(lián)的Condition對(duì)象,鎖對(duì)象也支持等待/喚醒機(jī)制。
              相對(duì)于隱含鎖,鎖對(duì)象最大的優(yōu)勢(shì)就是它們可以退回(backs out)試圖去獲得某個(gè)鎖。如果一個(gè)鎖不能立刻或在一個(gè)時(shí)間限制(如果指定了)之前獲得的話,tryLock方法就會(huì)退回這一企圖。如果在獲得鎖之前,另一個(gè)線程發(fā)出了中斷信號(hào),lockInterruptibly方法也會(huì)退回這一請(qǐng)求。
              讓我們使用鎖對(duì)象去解決在Liveness這一節(jié)中看到的死鎖問(wèn)題。Alphonse和Gaston已經(jīng)訓(xùn)練了他們自己,能夠注意到朋友對(duì)鞠躬的反應(yīng)。我們使用這樣一種方法去做改進(jìn),即要求Friend對(duì)象在應(yīng)對(duì)鞠躬行為之前,必須獲得兩個(gè)參與者的鎖。下面的源代碼就是改進(jìn)后的模型,Safelock。為了證明該方式的通用性,我們假設(shè)Alphonse和Gaston是如此地癡迷于他們新發(fā)現(xiàn)的安全地鞠躬的能力,以至于相互之間都不能停止向?qū)Ψ骄瞎?br />     import java.util.concurrent.locks.Lock;
              import java.util.concurrent.locks.ReentrantLock;
              import java.util.Random;

              public class Safelock {
                  static class Friend {
                  private final String name;
                  private final Lock lock = new ReentrantLock();

                  public Friend(String name) {
                      this.name = name;
                  }

                  public String getName() {
                      return this.name;
                  }

                  public boolean impendingBow(Friend bower) {
                      Boolean myLock = false;
                      Boolean yourLock = false;
                      try {
                          myLock = lock.tryLock();
                          yourLock = bower.lock.tryLock();
                      } finally {
                          if (! (myLock && yourLock)) {
                                  if (myLock) {
                                      lock.unlock();
                                  }
                              if (yourLock) {
                                  bower.lock.unlock();
                              }
                          }
                      }
                      return myLock && yourLock;
                  }
                              
                  public void bow(Friend bower) {
                      if (impendingBow(bower)) {
                          try {
                              System.out.format("%s: %s has bowed to me!%n",
                                  this.name, bower.getName());
                              bower.bowBack(this);
                          } finally {
                              lock.unlock();
                              bower.lock.unlock();
                          }
                      } else {
                          System.out.format("%s: %s started to bow to me, but" +
                              " saw that I was already bowing to him.%n",
                              this.name, bower.getName());
                          }
                      }

                      public void bowBack(Friend bower) {
                          System.out.format("%s: %s has bowed back to me!%n",
                              this.name, bower.getName());
                      }
                  }

                  static class BowLoop implements Runnable {
                      private Friend bower;
                      private Friend bowee;

                      public BowLoop(Friend bower, Friend bowee) {
                          this.bower = bower;
                          this.bowee = bowee;
                      }

                      public void run() {
                          Random random = new Random();
                              for (;;) {
                          try {
                              Thread.sleep(random.nextInt(10));
                          } catch (InterruptedException e) {}
                              bowee.bow(bower);
                          }
                      }
                  }

                  public static void main(String[] args) {
                      final Friend alphonse = new Friend("Alphonse");
                      final Friend gaston = new Friend("Gaston");
                      new Thread(new BowLoop(alphonse, gaston)).start();
                      new Thread(new BowLoop(gaston, alphonse)).start();
                  }
              }

          執(zhí)行器
              在所有之前的例子中,在由新線程執(zhí)行的工作--由它的Runnable對(duì)象定義,和該線程本身--由Thread對(duì)象定義,之間有著緊密的聯(lián)系。對(duì)于小規(guī)模應(yīng)用,它能工作的很好,但在大規(guī)模應(yīng)用中,就有必要將線程的管理與創(chuàng)建從應(yīng)用的其它部分分離出來(lái)。封閉這部分功能的對(duì)象被稱作執(zhí)行器。下面的子章節(jié)將細(xì)致地描述執(zhí)行器。
              * Executor接口定義了三種執(zhí)行器類型。
              * Thread Pool是執(zhí)行器最常用的實(shí)現(xiàn)。
          Executor接口
              java.util.concurrent包定義了三種執(zhí)行器接口:
              * Executor,是一種支持啟動(dòng)新任務(wù)的簡(jiǎn)單接口。
              * ExecutorService,是Executor接口的子接口,它加入了幫助管理包括單個(gè)任務(wù)和執(zhí)行器本身的生命周期的功能。
              * ScheduledExecutorService,是ExecutorService的子接口,支持在未來(lái)時(shí)間和/或周期性執(zhí)行任務(wù)。
              一般地,與執(zhí)行器對(duì)象相關(guān)的變量都被聲明為上述三個(gè)接口中的一個(gè),而不是一個(gè)執(zhí)行器類。
              Executor接口
              Executor接口提供了一個(gè)方法,execute,它被設(shè)計(jì)為是通用的線程創(chuàng)建方式的替代品。如果r是一個(gè)Runnable對(duì)象,那么e就是你用于替換r的Executor對(duì)象
              (new Thread(r)).start();
              和
              e.execute(r);
              然而,execute方法的定義缺乏規(guī)范。前面的低層次機(jī)制創(chuàng)建了一個(gè)新的線程并立即啟動(dòng)它。但根據(jù)Executor的不同實(shí)現(xiàn),execute可能也會(huì)做相同的事情,但更可能是使用一個(gè)已有的工人(worker)線程去執(zhí)行r,或?qū)置于一個(gè)等待工人線程能被使用的隊(duì)列中。(我們將在Thread Pool一節(jié)中描述工人線程。)
              java.util.concurrent包中的執(zhí)行器實(shí)現(xiàn)被設(shè)計(jì)為使更高級(jí)的ExecutorService和ScheduledExecutorService能夠充分使用它,盡管它們也能夠與Executor接口一起工作。
              ExecutorService接口
              ExecutorService接口補(bǔ)充提供了一個(gè)與execute相似但功能更豐富的submit方法。與execute相同,submit方法也接受Runnable對(duì)象,但也接受Callable對(duì)象,該對(duì)象允許任務(wù)返回一個(gè)值。submit方法返回Future對(duì)象,該對(duì)象用于獲取Callable返回的值并管理Callable和Runnable任務(wù)的狀態(tài)。
              ExecutorService也提供了用于提交大量Callable對(duì)象集合的方法。最后,ExecutorService還提供了用于管理執(zhí)行器關(guān)閉的一組方法。為了支持立即關(guān)閉,任務(wù)應(yīng)該要正確地處理中斷。
              ScheduledExecutorService接口
              ScheduledExecutorService接口為它的父接口補(bǔ)充提供了與時(shí)間計(jì)劃有關(guān)的方法,使得能在指定延遲后執(zhí)行Runnable或Callable任務(wù)。
          線程池
              java.util.concurrent包中的大部分執(zhí)行器實(shí)現(xiàn)都使用了由工人(worker)線程組成的線程池。這種線程獨(dú)立于Runnable和Callable任務(wù)而存在,常被用于執(zhí)行多個(gè)任務(wù)。
              使用工人線程能夠最大限度的減小由于線程創(chuàng)建而產(chǎn)生的開(kāi)銷。線程對(duì)象會(huì)占用大量的內(nèi)存,而在大規(guī)模應(yīng)用中,分配和收回大量線程對(duì)象會(huì)造成大量的內(nèi)存管理開(kāi)銷。
              一種常用的線程池類型是固定數(shù)量線程池。這種池總是有特定數(shù)量的線程在執(zhí)行;如果一個(gè)線程不知何故在它仍然被使用時(shí)終止了,它會(huì)立即被一個(gè)新的線程替代。任務(wù)通過(guò)內(nèi)部隊(duì)列提交到線程池中。當(dāng)活躍任務(wù)的數(shù)量超過(guò)線程的數(shù)量時(shí),這種內(nèi)部隊(duì)列會(huì)保存多余的任務(wù)。
              固定數(shù)量線程池的一個(gè)重要的優(yōu)點(diǎn)就是應(yīng)用會(huì)慢慢退化地使用它。為了理解這一點(diǎn),考慮這樣的一個(gè)Web服務(wù)器的應(yīng)用,每個(gè)HTTP請(qǐng)求被獨(dú)立的線程處理。如果應(yīng)用為每個(gè)新的HTTP請(qǐng)求創(chuàng)建一個(gè)新的線程,并且系統(tǒng)接到的請(qǐng)求數(shù)超出它能立即處理的數(shù)量,即當(dāng)所有線程的開(kāi)銷超過(guò)系統(tǒng)的承受能力時(shí),該應(yīng)用對(duì)此的反應(yīng)就會(huì)是突然停止 。
              一種簡(jiǎn)單地創(chuàng)建執(zhí)行器的方法就是使用固定數(shù)量線程池,通過(guò)調(diào)用java.util.concurrent.Executors類的newFixedThreadPool工廠方法可以得到該線程池。Executors類也提供下面的工廠方法:
              * newCachedThreadPool方法創(chuàng)建一個(gè)有可擴(kuò)展線程池的執(zhí)行器。該執(zhí)行器適用于會(huì)啟動(dòng)許多短壽命任務(wù)的應(yīng)用。
              * newSingleThreadExecutor方法創(chuàng)建在一個(gè)時(shí)間點(diǎn)只執(zhí)行一個(gè)任務(wù)的執(zhí)行器。
              * 有幾個(gè)工廠方法是上述執(zhí)行器的ScheduledExecutorService版本。
              如果上述工廠方法提供的執(zhí)行器沒(méi)有一個(gè)適合于你的需求,創(chuàng)建java.util.concurrent.ThreadPoolExecutor或java.util.concurrent.ScheduledThreadPoolExecutor的實(shí)例將給你另外的選擇。

          并發(fā)集合
              java.util.concurrent包含一組Java集合框架額外的擴(kuò)展。根據(jù)提供的集合接口十分容易把它們歸類為:
              * BlockingQueue定義了一個(gè)先入先出的數(shù)據(jù)結(jié)構(gòu),當(dāng)你試圖向一個(gè)已滿的隊(duì)列添加或向從一個(gè)已空的隊(duì)列中取出元素時(shí),阻塞你或使你超時(shí)。
              * ConcurrentMap是java.util.Map的子接口,它定義了一些有用的原子操作。只有某個(gè)鍵存在時(shí),這些操作才刪除或替換一個(gè)這個(gè)鍵-值對(duì),或者只有當(dāng)某個(gè)鍵不存在時(shí),才能添加這個(gè)鍵-值對(duì)。使這些操作都是原子的,以幫助避免同步。標(biāo)準(zhǔn)而通用的ConcurrentMap實(shí)現(xiàn)是ConcurrentHashMap,它是HashMap的同步相似體。
              * ConcurrentNavigableMap is a subinterface of ConcurrentMap that supports approximate matches. The standard general-purpose implementation of ConcurrentNavigableMap is ConcurrentSkipListMap, which is a concurrent analog of TreeMap.
              * ConcurrentNavigableMap是ConcurrentMap的子接口,它支持近似符合。標(biāo)準(zhǔn)而通用的ConcurrentNavigableMap是ConcurrentSkipListMap,它是TreeMap的同步相似體。
              所有這些集合都為了幫助避免內(nèi)存一致性錯(cuò)誤而在向集合中添加對(duì)象的操作與其后的訪問(wèn)或刪除對(duì)象的操作之間定義了"Happens-Before"關(guān)系。

          原子變量
              java.util.concurrent.atomic包定義了在單個(gè)變量中支持原子操作的類。所有的這些類都有g(shù)et和set方法,這些方法就如同讀寫volatile變量那樣工作。即,一個(gè)set方法與任何隨其后的針對(duì)相同變量的get方法之間有"Happen-Before"對(duì)象。通過(guò)應(yīng)用于整型原子變量的原子算術(shù)方法,原子的compareAndSet方法也戰(zhàn)士具有這樣的內(nèi)存一致性特性。
              為了看看如何使用這個(gè)包,讓我們回想之前為了證明干預(yù)而使用過(guò)的類Counter:   
              class Counter {
                  private int c = 0;

                  public void increment() {
                      c++;
                  }

                  public void decrement() {
                      c--;
                  }

                  public int value() {
                      return c;
                  }
              }
              為了防止線程干預(yù)的一種方法就是使它的方法可同步,如SynchronizedCounter里的方法那樣:
              class SynchronizedCounter {
                  private int c = 0;

                  public synchronized void increment() {
                      c++;
                  }

                  public synchronized void decrement() {
                      c--;
                  }

                  public synchronized int value() {
                      return c;
                  }
              }
              對(duì)于這個(gè)簡(jiǎn)單的類,同步是一個(gè)能夠被接受的解決方案。但對(duì)于更復(fù)雜的類,我們可能想避免不必要的同步的活躍度影響。使用AtomicInteger對(duì)象替代int字段允許我們?cè)诓磺笾降那闆r下就能防止線程干預(yù)。
              import java.util.concurrent.atomic.AtomicInteger;

              class AtomicCounter {
                  private AtomicInteger c = new AtomicInteger(0);

                  public void increment() {
                      c.incrementAndGet();
                  }

                  public void decrement() {
                      c.decrementAndGet();
                  }

                  public int value() {
                      return c.get();
                  }
              }
          進(jìn)一步地閱讀
              * Concurrent Programming in Java: Design Principles and Pattern (2nd Edition),Doug Lea著。這本綜合性著作的作者是一位卓越的專家,同時(shí)也是Java平臺(tái)并發(fā)框架的架構(gòu)師。
              * Java Concurrency in Practice,Brian Goetz,Tim Peierls,Joshua Bloch,Joseph Bowbeer,David Holmes和Doug Lea著。一本為貼近初學(xué)者而設(shè)計(jì)的實(shí)踐性指南。
              * Effective Java Programming Language Guide,Joshua Bloch著。雖然這是一本通用的程序設(shè)計(jì)指南,但其中關(guān)于線程的章節(jié)包含著并發(fā)編程必需的"最佳實(shí)踐"。
              * Concurrency: State Models & Java Programs (2nd Edition),Jeff Magee和eff Kramer著。通過(guò)模型化和實(shí)用的例子介紹了并發(fā)編程。

          posted on 2007-10-28 19:51 John Jiang 閱讀(2740) 評(píng)論(5)  編輯  收藏 所屬分類: JavaSEJavaConcurrency翻譯JavaTutorials

          評(píng)論

          # re: Java Tutorials -- Concurrency(譯) 2007-11-04 20:51 Sha Jiang
          最近忙于準(zhǔn)備和參加Sun Tech Days,故未作更新。
          下周會(huì)盡力更新部分內(nèi)容,但本章的內(nèi)容較多,還是要慢慢來(lái) ^_^  回復(fù)  更多評(píng)論
            

          # re: Java Tutorials -- Concurrency(譯) 2008-01-09 16:56 zhqworld
          看了同步方法和同步對(duì)象這一段,感覺(jué)翻譯的還可以,不過(guò)有的地方比較生硬,但像樓主這樣踏實(shí)求學(xué)的人現(xiàn)在的確不多了,期待更多好文,謝謝  回復(fù)  更多評(píng)論
            

          # re: Java Tutorials -- Concurrency(譯) 2008-01-09 20:13 sitinspring
          佩服。  回復(fù)  更多評(píng)論
            

          # re: Java Tutorials -- Concurrency(譯) 2008-01-10 09:17 Sha Jiang
          > 看了同步方法和同步對(duì)象這一段,感覺(jué)翻譯的還可以,不過(guò)有的地方比較生硬
          我有個(gè)"毛病",就是首選直譯。如果直譯能說(shuō)通,就不用音譯了。
          這樣的話,就會(huì)有一些語(yǔ)句比較生硬了。
          不過(guò),我是在學(xué)習(xí)翻譯的過(guò)程中,對(duì)于把握"直譯"或"意譯"的這個(gè)"度"也是正在提高中... *_*  回復(fù)  更多評(píng)論
            

          # re: Java Tutorials -- Concurrency(譯) 2008-04-14 13:59 Sha Jiang
          > 如果直譯能說(shuō)通,就不用音譯了。
          有一處筆誤:"音譯"應(yīng)為"意譯"。  回復(fù)  更多評(píng)論
            

          主站蜘蛛池模板: 沅陵县| 四子王旗| 黑水县| 安吉县| 油尖旺区| 璧山县| 古交市| 崇左市| 盐津县| 明星| 鄂托克旗| 泰和县| 孝昌县| 涟源市| 镇平县| 麟游县| 黔西| 旬邑县| 新野县| 奉贤区| 濮阳市| 扎赉特旗| 北辰区| 沈丘县| 明水县| 贺兰县| 山阴县| 松滋市| 云和县| 英吉沙县| 罗江县| 囊谦县| 汉寿县| 韩城市| 泗洪县| 龙山县| 民勤县| 临汾市| 聊城市| 商水县| 孝感市|