xylz,imxylz

          關(guān)注后端架構(gòu)、中間件、分布式和并發(fā)編程

             :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            111 隨筆 :: 10 文章 :: 2680 評論 :: 0 Trackbacks

          常見的并發(fā)陷阱

          volatile

          volatile只能強調(diào)數(shù)據(jù)的可見性,并不能保證原子操作和線程安全,因此volatile不是萬能的。參考指令重排序

          volatile最常見于下面兩種場景。

          a. 循環(huán)檢測機制

          volatile boolean done = false;


              while( ! done ){
                  dosomething();
              }


          b. 單例模型 (http://www.aygfsteel.com/xylz/archive/2009/12/18/306622.html)

          public class DoubleLockSingleton {

              
          private static volatile DoubleLockSingleton instance = null;

              
          private DoubleLockSingleton() {
              }

              
          public static DoubleLockSingleton getInstance() {
                  
          if (instance == null) {
                      
          synchronized (DoubleLockSingleton.class) {
                          
          if (instance == null) {
                              instance 
          = new DoubleLockSingleton();
                          }
                      }
                  }
                  
          return instance;
              }
          }

           


          synchronized/Lock

          看起來Lock有更好的性能以及更靈活的控制,是否完全可以替換synchronized?

          鎖的一些其它問題中說過,synchronized的性能隨著JDK版本的升級會越來越高,而Lock優(yōu)化的空間受限于CPU的性能,很有限。另外JDK內(nèi)部的工具(線程轉(zhuǎn)儲)對synchronized是有一些支持的(方便發(fā)現(xiàn)死鎖等),而對Lock是沒有任何支持的。

          也就說簡單的邏輯使用synchronized完全沒有問題,隨著機器的性能的提高,這點開銷是可以忽略的。而且從代碼結(jié)構(gòu)上講是更簡單的。簡單就是美。

          對于復(fù)雜的邏輯,如果涉及到讀寫鎖、條件變量、更高的吞吐量以及更靈活、動態(tài)的用法,那么就可以考慮使用Lock。當(dāng)然這里尤其需要注意Lock的正確用法。

          Lock lock = 
          lock.lock();
          try{
              //do something
          }finally{
              lock.unlock();
          }


          一定要將Lock的釋放放入finally塊中,否則一旦發(fā)生異常或者邏輯跳轉(zhuǎn),很有可能會導(dǎo)致鎖沒有釋放,從而發(fā)生死鎖。而且這種死鎖是難以排查的。

          如果需要synchronized無法做到的嘗試鎖機制,或者說擔(dān)心發(fā)生死鎖無法自恢復(fù),那么使用tryLock()是一個比較明智的選擇的。

          Lock lock = 
          if(lock.tryLock()){
              try{
                  //do something
              }finally{
                  lock.unlock();
              }
          }

           

          甚至可以使用獲取鎖一段時間內(nèi)超時的機制Lock.tryLock(long,TimeUnit)。 鎖的使用可以參考前面文章的描述和建議。

          鎖的邊界

          一個流行的錯誤是這樣的。

          ConcurrentMap<String,String> map = new ConcurrentHashMap<String,String>();

          if(!map.containsKey(key)){
              map.put(key,value);
          }


          看起來很合理的,對于一個線程安全的Map實現(xiàn),要存取一個不重復(fù)的結(jié)果,先檢測是否存在然后加入。 其實我們知道兩個原子操作和在一起的指令序列不代表就是線程安全的。 割裂的多個原子操作放在一起在多線程的情況下就有可能發(fā)生錯誤。

          實際上ConcurrentMap提供了putIfAbsent(K, V)的“原子操作”機制,這等價于下面的邏輯:

          if(map.containsKey(key)){
              return map.get(key);
          }else{
              return map.put(k,v);
          }


          除了putIfAbsent還有replace(K, V)以及replace(K, V, V)兩種機制來完成組合的操作。

          提到Map,這里有一篇談HashMap讀寫并發(fā)的問題。

          構(gòu)造函數(shù)啟動線程

          下面的實例是在構(gòu)造函數(shù)中啟動一個線程。

          public class Runner{
             int x,y;
             Thread thread;
             public Runner(){
                this.x=1;
                this.y=2;
                this.thread=new MyThread();
                this.thread.start();
             }
          }


          這里可能存在的陷阱是如果此類被繼承,那么啟動的線程可能無法正確讀取子類的初始化操作。

          因此一個簡單的原則是,禁止在構(gòu)造函數(shù)中啟動線程,可以考慮但是提供一個方法來啟動線程。如果非要這么做,最好將類設(shè)置為final,禁止繼承。

          丟失通知的問題

          這篇文章里面提到過notify丟失通知的問題。

          對于wait/notify/notifyAll以及await/singal/singalAll,如果不確定到底是否能夠正確的收到消息,擔(dān)心丟失通知,簡單一點就是總是通知所有。

          如果擔(dān)心只收到一次消息,使用循環(huán)一直監(jiān)聽是不錯的選擇。

          非常主用性能的系統(tǒng),可能就需要區(qū)分到底是通知單個還是通知所有的掛起者。

          線程數(shù)

          并不是線程數(shù)越多越好,在下一篇文章里面會具體了解下性能和可伸縮性。 簡單的說,線程數(shù)多少沒有一個固定的結(jié)論,受限于CPU的內(nèi)核數(shù),IO的性能以及依賴的服務(wù)等等。因此選擇一個合適的線程數(shù)有助于提高吞吐量。

          對于CPU密集型應(yīng)用,線程數(shù)和CPU的內(nèi)核數(shù)一致有助于提高吞吐量,所有CPU都很繁忙,效率就很高。 對于IO密集型應(yīng)用,線程數(shù)受限于IO的性能,某些時候單線程可能比多線程效率更高。但通常情況下適當(dāng)提高線程數(shù),有利于提高網(wǎng)絡(luò)IO的效率,因為我們總是認(rèn)為網(wǎng)絡(luò)IO的效率比較低。

          對于線程池而言,選擇合適的線程數(shù)以及任務(wù)隊列是提高線程池效率的手段。

          public ThreadPoolExecutor(
              int corePoolSize,
              int maximumPoolSize,
              long keepAliveTime,
              TimeUnit unit,
              BlockingQueue<Runnable> workQueue,
              ThreadFactory threadFactory,
              RejectedExecutionHandler handler)

           


          對于線程池來說,如果任務(wù)總是有積壓,那么可以適當(dāng)提高corePoolSize大小;如果機器負(fù)載較低,那么可以適當(dāng)提高maximumPoolSize的大小;任務(wù)隊列不長的情況下減小keepAliveTime的時間有助于降低負(fù)載;另外任務(wù)隊列的長度以及任務(wù)隊列的拒絕策略也會對任務(wù)的處理有一些影響。

           



          ©2009-2014 IMXYLZ |求賢若渴
          posted on 2011-12-30 17:25 imxylz 閱讀(6934) 評論(0)  編輯  收藏 所屬分類: Java Concurrency

          ©2009-2014 IMXYLZ
          主站蜘蛛池模板: 邵东县| 晴隆县| 南澳县| 罗田县| 仪征市| 卢龙县| 闸北区| 潜江市| 托克逊县| 凤城市| 新安县| 靖安县| 姜堰市| 鄂托克前旗| 精河县| 宜兰县| 独山县| 缙云县| 阳泉市| 开江县| 韶关市| 兴隆县| 武功县| 巴彦县| 双柏县| 原平市| 琼海市| 游戏| 驻马店市| 盐池县| 南昌县| 松潘县| 新平| 西乌珠穆沁旗| 永吉县| 维西| 丰台区| 尼勒克县| 曲沃县| 漾濞| 安西县|