bomberlhl

          統計

          留言簿(1)

          閱讀排行榜

          評論排行榜

          2009年12月14日 #

          【轉】使用synchronized進行Java線程同步

          線程同步指多個線程同時訪問某資源時,采用一系列的機制以保證同時最多只能一個線程訪問該資源。
          為什么需要線程同步呢?
          我們舉一個最簡單的例子來說明為什么需要線程同步。
          比如有一本書(有且只有一本),交給多個售貨員同時去賣;
          如果其中任何一個售貨員把這本書給賣了,其他售貨員就不能再賣這本書了。
          現實生活中,如果要保證該書不會被多個售貨員同時賣掉,必須要有一種機制來保證:
          比如,售貨員應該拿到該書之后才能開始賣書,暫時拿不到的話就只能等該書被退回柜臺。

          售書的完整的例子可以參考 范例解說Java里的線程概念與線程同步技術 一文

          這里,每一個售貨員售書可以看作一個線程。欲售的書便是各線程需要共享的資源。
          開始售書之前,需要取得該書(資源),取不到情況下等待:資源取得
          開始售書之后,則需要取得對該書的獨享控制(不讓他人拿到該書):資源加鎖
          售完書時,需要通知柜臺該書已售出;或者未售出時,把書退回柜臺(通知他人可以拿到該書):資源解鎖

          synchronized控制線程同步的概念跟此完全一樣。
          Java里可以使用synchronized來同步代碼塊或者方法。
          同步代碼塊例:
          1. synchronized(欲同步的對象obj) {
          2.     需要同步的代碼塊   
          3. }  

          可以同步代碼塊。

          synchronized (obj) 表示若多個線程同時訪問時,只讓其中一個線程最先取得obj對象并對其加鎖,其它線程則阻塞直到取得obj對象的線程執行完代碼塊,此時被加鎖的obj對象得到釋放(解鎖),其它線程得到通知取得該book對象繼續執行。
          很多情況下,可以使用synchronized (this){...}來同步代碼塊。但需要注意的是,使用this作為同步對象的話,如果同一個類中存在多個synchronized (this){...}代碼塊,其中任何一個synchronized(this)代碼塊處于被執行狀態,則其它線程對其他synchronized(this)代碼塊的訪問也會受到阻塞。
          為了說明這個問題,我們舉例說明:

          HelloSynchronized.java
          1. publicclass HelloSynchronized {   
          2. publicstaticvoid main(String[] args) {   
          3. //
          4.         HelloSynchronized helloSynchronized = new HelloSynchronized();   
          5. //創建2個線程t1, t2,分別調用HelloSynchronized helloSynchronized的2個方法method1,與method2
          6.         Thread t1 = new Thread(new HelloSynchronizedRunnalbe(helloSynchronized, "method1"), "t1");   
          7.         Thread t2 = new Thread(new HelloSynchronizedRunnalbe(helloSynchronized, "method2"), "t2");   
          8.         t1.start();   
          9.         t2.start();   
          10.     }   
          11. //synchronized public void method1() {    //同步方法
          12. publicvoid method1() {   
          13. synchronized (this) {    //同步塊
          14.             System.out.println(Thread.currentThread().getName()
          15.                     + " enter method1");   
          16. try {
          17.                 Thread.sleep(3000);   
          18.             } catch (InterruptedException e) {
          19. // do nothing
          20.             }   
          21.             System.out.println(Thread.currentThread().getName()
          22.                     + " exit method1");   
          23.         }   
          24.     }   
          25. //synchronized public void method2() {    //同步方法
          26. publicvoid method2() {   
          27. synchronized (this) {    //同步塊
          28.             System.out.println(Thread.currentThread().getName()
          29.                     + " enter method2");   
          30. try {
          31.                 Thread.sleep(3000);   
          32.             } catch (InterruptedException e) {
          33. // do nothing
          34.             }   
          35.             System.out.println(Thread.currentThread().getName()
          36.                     + " exit method2");   
          37.         }   
          38.     }   
          39. }   
          40. class HelloSynchronizedRunnalbe implements Runnable {   
          41. private HelloSynchronized helloSynchronized;
          42. private String methodName;   
          43. public HelloSynchronizedRunnalbe(HelloSynchronized helloSynchronized, String methodName) {
          44. this.helloSynchronized = helloSynchronized;
          45. this.methodName = methodName;   
          46.     }   
          47. publicvoid run() {   
          48. if (methodName.equals("method1")) {   
          49.             helloSynchronized.method1();   
          50.         } elseif (methodName.equals("method2")) {   
          51.             helloSynchronized.method2();   
          52.         }   
          53.     }   
          54. }  


          運行結果為:
          t1 enter method1
          t1 exit method1
          t2 enter method2
          t2 exit method2
          等到線程t1結束后,t2才開始運行(t2受到阻塞)

          再把synchronized (this)去掉,運行結果為:
          t1 enter method1
          t2 enter method2
          t1 exit method1
          t2 exit method2
          線程t1,t2同時運行

          同步方法例:
          1. synchronizedprivatevoid sellBook(Book book) {   
          2. ...   
          3. }  

          這種方法其實相當于
          1. privatevoid sellBook(Book book) {   
          2. synchronized(this) {   
          3.         ...   
          4.     }   
          5. }  

          由于默認采用this作為同步對象,所以當一個類中有多個synchronized方法時,同樣會存在以上問題:即如果有一個線程訪問其中某個synchronized方法時,直到該方法執行完畢,其它線程對其它synchronized方法的訪問也將受到阻塞。
          大家可以把上面的例子稍加改造,去掉代碼中的synchronized (this),改為synchronized public void method1(),synchronized public void method2()同步形式,運行后會得到同樣結果。

          多同步代碼塊synchronized(this){...}的多線程阻塞問題(包括synchronized同步方法),在并發處理的系統中(比如WEB服務器)會嚴重影響性能,建議慎重使用。可以使用synchronized(obj){...}縮小同步資源對象的范圍來解決這個問題。

          posted @ 2009-12-14 19:33 異域流浪 閱讀(288) | 評論 (0)編輯 收藏

          【轉】范例解說Java里的線程概念與線程同步技術

          線程 是一段完成某個特定功能的代碼,程序中的執行線程。Java 虛擬機允許應用程序并發地運行多個執行線程。
          每個線程都有一個優先級,高優先級線程的執行優先于低優先級線程。
          進程不同的是,由同名類生成的多個線程共享相同的內存空間和系統資源。

          線程與進程的區別:
          一個線程是一個程序內部的順序控制流。
          1. 進程:每個進程都有獨立的代碼和數據空間(進程上下文) ,進程切換的開銷大。線程:同一類線程共享代碼和數據空間,每個線程有獨立的運行棧和程序計數器(PC),線程切換的開銷小。
          2. 一個進程中可以包含多個線程。

          本文將介紹以下線程方面的知識:
          1,線程的創建
          2,線程的狀態
          3,線程同步
          4,線程組

          理解線程的最有效的方法是通過實例來理解。下面我們將通過 售貨員售書 為例,由淺入深地介紹線程的創建,通信,鎖機制等概念。

          售貨員售書
          我們假設一下售貨員售書的操作流程:
          1,我們假設有20本書,交給2個售貨員去賣。
          2,售貨員可以賣掉任何一本尚未賣出去的書。換句話說,同一本書若被其中一位售出去了,則不能被另外一位再售出了。


          清單1:
          文件名說明
          Book.java 書籍類
          SellBookRunnable.java 售書類,線程的創建方法之一,該類實現了Runnable 接口,并實現了 run 方法。
          SellBookThread.java 售書類,線程的創建方法之一,該類聲明為 Thread 的子類,并重寫 Thread 類的 run 方法。
          CallSellBook.java 調用類。該類分別介紹了2種不同線程創建的調用方法。

          Book.java
          1. publicclass Book {   
          2. private String name;
          3. privateboolean sold = false;   
          4. public Book(String name) {   
          5. this.name = name;
          6.     }   
          7. public String getName() {
          8. return name;
          9.     }   
          10. publicvoid setName(String name) {   
          11. this.name = name;   
          12.     }   
          13. publicboolean isSold() {   
          14. return sold;
          15.     }   
          16. publicvoid setSold(boolean sold) {   
          17. this.sold = sold;   
          18.     }   
          19. }  



          SellBookRunnable.javatil.List;
          1. publicclass SellBookRunnable implements Runnable {   
          2. private String saleMan;
          3. private List<Book> bookList;
          4. public SellBookRunnable(String saleMan, List<Book> bookList) {
          5. this.saleMan = saleMan;   
          6. this.bookList = bookList;   
          7.     }   
          8. publicvoid run() {   
          9. for (int i = 0; i < bookList.size(); i++) {
          10.             Book book = bookList.get(i);   
          11.             sellBook(book);   
          12.         }   
          13.     }   
          14. /**  
          15.      * 售貨員賣書。我們這樣描述售貨員的賣書過程。  
          16.      *   
          17.      * @param book Book  
          18.      */
          19. privatevoid sellBook(Book book) {   
          20. //從開始售書-到售書完成,使用synchronized (book)保證book不被其他售貨員售出
          21. synchronized (book) {
          22. if (book.isSold()) {   
          23. return;
          24.             } else {
          25. try {
          26. //為了讓各線程有執行機會,設置平均售書時間為0.5秒
          27.                     Thread.sleep(500);   
          28.                 } catch (Exception e) {   
          29.                 }   
          30. //設置已售標志
          31.                 book.setSold(true);   
          32. //打印該書已售信息
          33.                 System.out.println("[" + saleMan + "]" + book.getName() + " sold out:"
          34.                         + book.isSold() + ". by "
          35.                         + Thread.currentThread().getName());
          36.             }   
          37.         }   
          38.     }   
          39. }  



          SellBookThread.java

          1. import java.util.List;
          2. publicclass SellBookThread extends Thread {   
          3. private String saleMan;
          4. private List<Book> bookList;
          5. public SellBookThread(String saleMan, List<Book> bookList) {
          6. this.saleMan = saleMan;   
          7. this.bookList = bookList;   
          8.     }   
          9. publicvoid run() {   
          10. for (int i = 0; i < bookList.size(); i++) {
          11.             Book book = bookList.get(i);   
          12.             sellBook(book);   
          13.         }   
          14.     }   
          15. /**  
          16.      * 售貨員賣書。我們這樣描述售貨員的賣書過程。  
          17.      *   
          18.      * @param book Book  
          19.      */
          20. privatevoid sellBook(Book book) {   
          21. //從開始售書-到售書完成,使用synchronized (book)保證book不被其他售貨員售出
          22. synchronized (book) {
          23. if (book.isSold()) {   
          24. return;
          25.             } else {
          26. try {
          27. //為了讓各線程有執行機會,設置平均售書時間為0.5秒
          28.                     Thread.sleep(500);   
          29.                 } catch (Exception e) {   
          30.                 }   
          31. //設置已售標志
          32.                 book.setSold(true);   
          33. //打印該書已售信息
          34.                 System.out.println("[" + saleMan + "]" + book.getName() + " sold out:"
          35.                         + book.isSold() + ". by "
          36.                         + Thread.currentThread().getName());
          37.             }   
          38.         }   
          39.     }   
          40. }  



          CallSellBook.java

          1. import java.util.ArrayList;
          2. import java.util.List;
          3. //該類調用SellBookXxx類
          4. publicclass CallSellBook {   
          5. /**  
          6.      * 用線程模擬這個售書的過程  
          7.      */
          8. publicstaticvoid main(String[] args) {   
          9. //方法1:
          10.         callSellBookThread();   
          11. //or
          12. //方法2:
          13. //callSellBookRunnable();
          14.     }   
          15. //調用SellBookRunnable(Runnable接口實現類)模擬售書過程
          16. publicstaticvoid callSellBookThread() {   
          17.         List <Book>bookList = getBookListForSale();
          18. //將預售書籍清單交給售貨員SaleMan1
          19.         Thread t1 = new SellBookThread("SaleMan1", bookList);   
          20. //將預售書籍清單交給售貨員SaleMan2
          21.         Thread t2 = new SellBookThread("SaleMan2", bookList);   
          22. //售貨員SaleMan1開始售書
          23.         t1.start();   
          24. //售貨員SaleMan2開始售書
          25.         t2.start();   
          26.     }   
          27. //調用SellBookRunnable(Runnable接口實現類)模擬售書過程
          28. publicstaticvoid callSellBookRunnable() {   
          29.         List <Book>bookList = getBookListForSale();
          30. //將預售書籍清單交給售貨員SaleMan1
          31.         Thread t1 = new Thread(new SellBookRunnable("SaleMan1", bookList));   
          32. //將預售書籍清單交給售貨員SaleMan2
          33.         Thread t2 = new Thread(new SellBookRunnable("SaleMan2", bookList));   
          34. //售貨員SaleMan1開始售書
          35.         t1.start();   
          36. //售貨員SaleMan2開始售書
          37.         t2.start();   
          38.     }   
          39. //準備預售書籍
          40. publicstatic List<Book> getBookListForSale() {
          41.         List <Book>bookList = new ArrayList();   
          42. for (int i = 0; i < 20; i++) {   
          43.             Book book = new Book("Book" + i);   
          44.             bookList.add(book);   
          45.         }   
          46. return bookList;   
          47.     }   
          48. }  


          執行CallSellBook
          [SaleMan1]Book0 sold out:true. by Thread-0
          [SaleMan2]Book1 sold out:true. by Thread-1
          [SaleMan2]Book2 sold out:true. by Thread-1
          [SaleMan2]Book3 sold out:true. by Thread-1
          [SaleMan2]Book4 sold out:true. by Thread-1
          [SaleMan2]Book5 sold out:true. by Thread-1
          [SaleMan1]Book6 sold out:true. by Thread-0
          [SaleMan1]Book7 sold out:true. by Thread-0
          [SaleMan1]Book8 sold out:true. by Thread-0
          [SaleMan1]Book9 sold out:true. by Thread-0
          [SaleMan1]Book10 sold out:true. by Thread-0
          [SaleMan1]Book11 sold out:true. by Thread-0
          [SaleMan2]Book12 sold out:true. by Thread-1
          [SaleMan2]Book13 sold out:true. by Thread-1
          [SaleMan2]Book14 sold out:true. by Thread-1
          [SaleMan2]Book15 sold out:true. by Thread-1
          [SaleMan2]Book16 sold out:true. by Thread-1
          [SaleMan2]Book17 sold out:true. by Thread-1
          [SaleMan1]Book18 sold out:true. by Thread-0
          [SaleMan1]Book19 sold out:true. by Thread-0
          線程的創建
          創建新執行線程有兩種方法。
          方法一種方法是將類聲明為 Thread 的子類。該子類應重寫 Thread 類的 run 方法。事實上類Thread本身也實現了接口Runnable,所以我們可以同過繼承Thread類實現線程體。
          參考:SellBookThread.javaCallSellBook.java
          另一種方法是聲明實現 Runnable 接口的類。該類然后實現 run 方法。
          參考:SellBookRunnable.javaCallSellBook.java



          線程的狀態
          線程有四種狀態:創建狀態(New),可運行狀態(Runnable),阻塞狀態(Blocked),死亡狀態(Dead)。

          創建狀態(New):
          當執行完
          Thread t1 = new SellBookThread("SaleMan1", bookList);
          語句之后,則t1處于創建狀態(New)。此時t1并未真正運行。

          可運行狀態(Runnable):
          當Thread t1被創建,并執行完
          t1.start();
          語句之后,t1就處于可運行狀態(Runnable)。此時,系統為線程t1分配其所需的系統資源。并對t1加以調用(或者根據任務調度情況準備調用)。

          阻塞狀態(Blocked):
          由于以下原因:
          1) 調用了sleep()方法;
          2) 調用了suspend()方法(該方法已不推薦使用);
          3) 為等待條件鎖,調用wait()方法等;
          4) 輸入輸出,或消息發生阻塞;

          使得線程處于阻塞狀態(Blocked)。處于該狀態的線程即使處理器空閑,也不會得到執行。

          死亡狀態(Dead):
          死亡狀態(Dead)可以為自然死亡(線程運行完畢),或者調用了stop()方法(該方法已不推薦使用)。


          線程的優先級:
          可以通過Thread類的    
          void setPriority(int newPriority)
          方法為線程設置優先級。但是不能保證高優先級的線程就會被先運行。

          線程組:
          可以通過
          ThreadGroup group = new ThreadGroup(groupName);
          Thread t1 = new Thread(ThreadGroup g, Runnable r1);
          Thread t1 = new Thread(ThreadGroup g, Runnable r2);
          等方法把多個線程加到一個線程組里去,這樣可以通過ThreadGroup對這些線程進行某些統一操作,
          例如:group.interrupt();中斷該組所有線程。


          線程unchecked異常處理器:
          可以通過:
          public void static Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)
          方法為所有線程指定一個unchecked異常處理器,該處理器必須實現UncaughtExceptionHandler接口。


          線程同步:
          線程同步指多個線程同時訪問某資源時,采用一系列的機制以保證同時最多只能一個線程訪問該資源。
          線程同步是多線程中必須考慮和解決的問題,因為很可能發生多個線程同時訪問(主要是寫操作)同一資源,如果不進行線程同步,很可能會引起數據混亂,造成線程死鎖等問題。

          使用synchronized同步線程。
          在J2SE5.0之前,只能使用synchronized來同步線程。可以使用synchronized來同步代碼塊或者方法。
          同步代碼塊例:
          synchronized(欲同步的對象obj) {需要同步的代碼塊}可以同步代碼塊。

          參考:SellBookThread.java
          1. privatevoid sellBook(Book book) {   
          2. synchronized (book) {
          3.             ...   
          4.         }   
          5.     }  

          該例synchronized (book) 表示若多個線程同時訪問時,只讓其中一個線程最先取得book對象,其它線程則阻塞直到代碼塊執行完畢book對象被釋放后,其它線程才能取得該book對象繼續執行。
          很多情況下,可以使用synchronized (this){...}來同步代碼塊。但需要注意的是,使用this作為同步對象的話,如果同一個類中存在多個synchronized (this){...}代碼塊,其中任何一個synchronized(this)代碼塊處于被執行狀態,則其它線程對其他synchronized(this)代碼塊的訪問也會受到阻塞。

          同步方法例:
          1. synchronizedprivatevoid sellBook(Book book) {   
          2. ...   
          3. }  

          這種方法其實相當于
          1. privatevoid sellBook(Book book) {   
          2. synchronized(this) {   
          3.         ...   
          4.     }   
          5. }  

          由于默認采用this作為同步對象,所以當一個類中有多個synchronized方法時,同樣會存在以上問題:即如果有一個線程訪問其中某個synchronized方法時,直到該方法執行完畢,其它線程對其它synchronized方法的訪問也將受到阻塞。
          有關synchronized詳細說明我們將在其它文章中加以說明。


          使用java.util.concurrent.locks.ReentrantLock和java.util.concurrent.locks.ReentrantReadWriteLock類同步線程。
          J2SE5.0加入了ReentrantLock和ReentrantReadWriteLock可以對線程進行同步,這里舉一個最簡單的例子對其加以說明:
          1. class X {
          2. privatefinal ReentrantLock lock = new ReentrantLock();   
          3. // ...
          4. publicvoid m() {    
          5.      lock.lock();  // block until condition holds
          6. try {
          7. // ... method body
          8.      } finally {
          9.        lock.unlock()   
          10.      }   
          11.    }   
          12.  }   



          其它J2SE5.0新導入的有關線程的相關接口/類:
          java.util.concurrent.Future
          Future接口可以保持/取得異步執行的結果值

          java.util.concurrent.Callable
          類似于Runnable接口。但Runnable不能返回值,也不能拋出checked異常

          java.util.concurrent.ExecutorService
          該接口繼承了Executor接口。可以通過submit方法把Runnable,Callable對象轉換為Future 形式。

          java.util.concurrent.FutureTask
          該類實現了Runnable和Future接口。提供異步執行的取消以及異步執行結果的取得等功能。

          java.util.concurrent.Executor
          執行指定的Runnable對象

          java.util.concurrent.Executors
          工具類。提供靜態方法可以創建Executor,ExecutorService,Callable等對象。可以通過newCachedThreadPool()等方法簡單創建線程池。


          posted @ 2009-12-14 19:29 異域流浪 閱讀(253) | 評論 (0)編輯 收藏

          主站蜘蛛池模板: 双流县| 太白县| 土默特左旗| 张家界市| 龙海市| 蓬安县| 东台市| 鸡东县| 自治县| 江西省| 嘉黎县| 万年县| 纳雍县| 余江县| 石狮市| 永登县| 灌云县| 海丰县| 正镶白旗| 西丰县| 任丘市| 南召县| 汉源县| 平乐县| 岚皋县| 循化| 三河市| 威远县| 平邑县| 宜兰县| 岳池县| 镶黄旗| 贵港市| 阿瓦提县| 吉隆县| 隆化县| 平阳县| 沁阳市| 荃湾区| 普陀区| 广丰县|