kxbin
          成功留給有準備的人
          posts - 10,  comments - 35,  trackbacks - 0

          基于線程安全的一些原則來編程當然可以避免并發問題,但不是所有人都能寫出高質量的線程安全的代碼,并且如果代碼里到處都是線程安全的控制也極大地影響了代碼可讀性和可維護性。因此,Java平臺為了解決這個問題,提供了很多線程安全的類和并發工具,通過這些類和工具就能更簡便地寫線程安全的代碼。歸納一下有以下幾種:

          • 同步容器類
          • 并發容器類
          • 生產者和消費者模式
          • 阻塞和可中斷方法
          • Synchronizer

          這些類和方法的使用都可以從JDK DOC查到,但在具體使用中還是有很多問題需要注意

          同步容器類

          同步容器類就是一些經過同步處理了的容器類,比如List有Vector,Map有Hashtable,查看其源碼發現其保證線程安全的方式就是把每個對外暴露的存取方法用synchronized關鍵字同步化,這樣做我們立馬會想到有以下問題:
          1)性能有問題

          同步化了所有存取方法,就表明所有對這個容器對象的操作將會串行,這樣做來得倒是干凈,但性能的代價也是很可觀的

          2)復合操作問題

          同步容器類只是同步了單一操作,如果客戶端是一組復合操作,它就沒法同步了,依然需要客戶端做額外同步,比如以下代碼:

           

          1. public static Object getLast(Vector list) {  
          2.     int lastIndex = list.size() - 1;  
          3.     return list.get(lastIndex);  
          4. }  
          5. public static void deleteLast(Vector list) {  
          6.     int lastIndex = list.size() - 1;  
          7.     list.remove(lastIndex);  
          8. }  

           

          getLast和deleteLast都是復合操作,由先前對原子性的分析可以判斷,這依然存在線程安全問題,有可能會拋出ArrayIndexOutOfBoundsException的異常,錯誤產生的邏輯如下所示:

           

          解決辦法就是通過對這些復合操作加鎖

          3)迭代器并發問題

          Java Collection進行迭代的標準時使用Iterator,無論是使用老的方式迭代循環,還是Java 5提供for-each新方式,都需要對迭代的整個過程加鎖,不然就會有Concurrentmodificationexception異常拋出。

          此外有些迭代也是隱含的,比如容器類的toString方法,或containsAll, removeAll, retainAll等方法都會隱含地對容器進行迭代

          并發容器類

          正是由于同步容器類有以上問題,導致這些類成了雞肋,于是Java 5推出了并發容器類,Map對應的有ConcurrentHashMap,List對應的有CopyOnWriteArrayList。與同步容器類相比,它有以下特性:

          • 更加細化的鎖機制。同步容器直接把容器對象做為鎖,這樣就把所有操作串行化,其實這是沒必要的,過于悲觀,而并發容器采用更細粒度的鎖機制,保證一些不會發生并發問題的操作進行并行執行
          • 附加了一些原子性的復合操作。比如putIfAbsent方法
          • 迭代器的弱一致性。它在迭代過程中不再拋出Concurrentmodificationexception異常,而是弱一致性。在并發高的情況下,有可能size和isEmpty方法不準確,但真正在并發環境下這些方法也沒什么作用。
          • CopyOnWriteArrayList采用寫入時復制的方式避開并發問題。這其實是通過冗余和不可變性來解決并發問題,在性能上會有比較大的代價,但如果寫入的操作遠遠小于迭代和讀操作,那么性能就差別不大了

          生產者和消費者模式

          大學時學習操作系統多會為生產者和消費者模式而頭痛,也是每次考試肯定會涉及到的,而Java知道大家很憷這個模式的并發復雜性,于是乎提供了阻塞隊列(BlockingQueue)來滿足這個模式的需求。阻塞隊列說起來很簡單,就是當隊滿的時候寫線程會等待,直到隊列不滿的時候;當隊空的時候讀線程會等待,直到隊不空的時候。實現這種模式的方法很多,其區別也就在于誰的消耗更低和等待的策略更優。以LinkedBlockingQueue的具體實現為例,它的put源碼如下:

           

          1. public void put(E e) throws InterruptedException {  
          2.     if (e == nullthrow new NullPointerException();  
          3.     int c = -1;  
          4.     final ReentrantLock putLock = this.putLock;  
          5.     final AtomicInteger count = this.count;  
          6.     putLock.lockInterruptibly();  
          7.     try {  
          8.         try {  
          9.             while (count.get() == capacity)  
          10.                 notFull.await();  
          11.         } catch (InterruptedException ie) {  
          12.             notFull.signal(); // propagate to a non-interrupted thread  
          13.             throw ie;  
          14.         }  
          15.         insert(e);  
          16.         c = count.getAndIncrement();  
          17.         if (c + 1 < capacity)  
          18.             notFull.signal();  
          19.     } finally {  
          20.         putLock.unlock();  
          21.     }  
          22.     if (c == 0)  
          23.         signalNotEmpty();  
          24. }  

           

          撇開其鎖的具體實現,其流程就是我們在操作系統課上學習到的標準生產者模式,看來那些枯燥的理論還是有用武之地的。其中,最核心的還是Java的鎖實現,有興趣的朋友可以再進一步深究一下

          阻塞和可中斷方法

          由LinkedBlockingQueue的put方法可知,它是通過線程的阻塞和中斷阻塞來實現等待的。當調用一個會拋出InterruptedException的方法時,就成為了一個阻塞的方法,要為響應中斷做好準備。處理中斷可有以下方法:

          • 傳遞InterruptedException。把捕獲的InterruptedException再往上拋,使其調用者感知到,當然在拋之前需要完成你自己應該做的清理工作,LinkedBlockingQueue的put方法就是采取這種方式
          • 中斷其線程。在不能拋出異常的情況下,可以直接調用Thread.interrupt()將其中斷。

          Synchronizer

          Synchronizer不是一個類,而是一種滿足一個種規則的類的統稱。它有以下特性:

          • 它是一個對象
          • 封裝狀態,而這些狀態決定著線程執行到某一點是通過還是被迫等待
          • 提供操作狀態的方法

          其實BlockingQueue就是一種Synchronizer。Java還提供了其他幾種Synchronizer

          1)CountDownLatch

          CountDownLatch是一種閉鎖,它通過內部一個計數器count來標示狀態,當count>0時,所有調用其await方法的線程都需等待,當通過其countDown方法將count降為0時所有等待的線程將會被喚起。使用實例如下所示:

           

          1. public class TestHarness {  
          2.     public long timeTasks(int nThreads, final Runnable task)  
          3.             throws InterruptedException {  
          4.         final CountDownLatch startGate = new CountDownLatch(1);  
          5.         final CountDownLatch endGate = new CountDownLatch(nThreads);  
          6.         for (int i = 0; i < nThreads; i++) {  
          7.             Thread t = new Thread() {  
          8.                 public void run() {  
          9.                     try {  
          10.                         startGate.await();  
          11.                         try {  
          12.                             task.run();  
          13.                         } finally {  
          14.                             endGate.countDown();  
          15.                         }  
          16.                     } catch (InterruptedException ignored) { }  
          17.                 }  
          18.             };  
          19.             t.start();  
          20.         }  
          21.         long start = System.nanoTime();  
          22.         startGate.countDown();  
          23.         endGate.await();  
          24.         long end = System.nanoTime();  
          25.         return end-start;  
          26.     }  
          27. }  

           

          2)Semaphore

          Semaphore類實際上就是操作系統中談到的信號量的一種實現,其原理就不再累述,可見探索并發編程------操作系統篇

          具體使用就是通過其acquire和release方法來完成,如以下示例:

           

          1. public class BoundedHashSet<T> {  
          2.     private final Set<T> set;  
          3.     private final Semaphore sem;  
          4.     public BoundedHashSet(int bound) {  
          5.         this.set = Collections.synchronizedSet(new HashSet<T>());  
          6.         sem = new Semaphore(bound);  
          7.     }  
          8.     public boolean add(T o) throws InterruptedException {  
          9.         sem.acquire();  
          10.         boolean wasAdded = false;  
          11.         try {  
          12.             wasAdded = set.add(o);  
          13.             return wasAdded;  
          14.         }  
          15.         finally {  
          16.             if (!wasAdded)  
          17.                 sem.release();  
          18.         }  
          19.     }  
          20.     public boolean remove(Object o) {  
          21.         boolean wasRemoved = set.remove(o);  
          22.         if (wasRemoved)  
          23.             sem.release();  
          24.         return wasRemoved;  
          25.     }  
          26. }  

           

          3)關卡

          關卡和閉鎖類似,也是阻塞一組線程,直到某件事情發生,而不同在于關卡是等到符合某種條件的所有線程都達到關卡點。具體使用上可以用CyclicBarrier來應用關卡

           

          以上是Java提供的一些并發工具,既然是工具就有它所適用的場景,因此需要知道它的特性,這樣才能在具體場景下選擇最合適的工具。

          posted on 2011-10-13 16:02 kxbin 閱讀(285) 評論(0)  編輯  收藏 所屬分類: java基礎
          你恨一個人是因為你愛他;你喜歡一個人,是因為他身上有你沒有的;你討厭一個人是因為他身上有你有的東西;你經常在別人面前批評某人,其實潛意識中是想接近他。

          <2025年6月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          常用鏈接

          留言簿(5)

          隨筆檔案

          文章分類

          文章檔案

          相冊

          收藏夾

          J2EE

          java技術網站

          Linux

          平時常去的網站

          數據庫

          電影網站

          網站設計

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 明光市| 临沂市| 松潘县| 阿克苏市| 鄂温| 赤水市| 宜丰县| 腾冲县| 铜山县| 广丰县| 清远市| 曲靖市| 安泽县| 沂源县| 咸宁市| 威远县| 开平市| 孝感市| 开封市| 沈阳市| 嘉荫县| 浙江省| 韩城市| 临高县| 开封县| 星子县| 丽水市| 库伦旗| 道孚县| 肃北| 光山县| 武邑县| 许昌市| 呼图壁县| 牡丹江市| 项城市| 罗田县| 徐闻县| 平南县| 眉山市| 文山县|