併發設計模式和鎖優化以及jdk8併發新特性

1 設計模式編程

(1) 單例模式 設計模式

     保證一個類只能一個對象實現。正常的單例模式分爲懶漢式和餓漢式,餓漢式就是把單例聲明稱static a=new A(),系統第一次調用的時候生成(包括調用該類的其餘靜態資源也會生成),懶漢式就是系統調用get函數的時候,加個鎖判斷單例對象是否存在,存在就返回不存在就聲明一個。好一點的懶漢式應該把單例加一個靜態內部類,第一次訪問的類的時候靜態內部類不會初始化,當調用的get方法的時候再實例化,這樣不用加鎖效率高一些,數組

 public class StaticSingleton {安全

   private StaticSingleton(){併發

     System.out.println("StaticSingleton is create");jvm

  }函數式編程

   private static class SingletonHolder {函數

     private static StaticSingleton instance = new StaticSingleton();高併發

   }性能

   public static StaticSingleton getInstance() {

     return SingletonHolder.instance;

   }

(2)不變模式  類和變量都聲明爲final,只要建立就不可變,常見的string Integer Double等都是不可變。

(3) future模式  客戶端請求服務端數據,若是服務端處理時間較長,能夠返回一個空值(相似代理),啓動一個線程專門設值。客戶端能夠先幹別的,當想要試用這個值時,能夠從代理裏拿,若是代理值已經設置好直接返回,若是沒設置好則wait,等設置好了的時候notify。  能夠向excutor裏提交一個實現了Collable的對象,會返回一個Future,而後使用這個future.get()拿值。

(4) 生產者消費者模式  專門有生產者生產數據,消費者消費數據,中間靠線程安全的隊列做爲公共區域,各線程都從這個區域裏寫值和讀值。各個線程無需瞭解對存在,只要負責本身的事情便可,也符合開閉原則。

2 鎖優化 

   具體思路:減小鎖持有時間,減少鎖粒度,鎖分離,鎖粗化,鎖消除。

    (1)減小鎖持有時間  儘可能少的加鎖代碼,例如用具體代碼段代替方法加鎖。 

  (2)減少鎖粒度   把大對象儘可能改爲小對象,增長並行度減小鎖競爭。同時有利於偏向鎖,輕量級鎖。例如ConcurrentHashMap

  (3)鎖分離   讀寫分離,讀讀可重入,讀寫互斥,寫寫互斥。另外一種分離,例如 LinkedBlockingQueue ,存數據和取數據從隊列兩端操做,兩端各自加鎖控制便可,兩端的鎖互不影響。

    (4)鎖粗化 若是一段程序要屢次請求鎖,鎖之間的代碼執行時間比較少,就應該整合成一個鎖,前提是不用同步的部分執行時間短。例如for循環裏面申請鎖,若是for循環時間不長,能夠在for外面加鎖。

    (5)鎖消除 編譯器級別的操做,若是jdk發現鎖不可能被共享,會擦除這個鎖。原理是逃逸分析,例如stringbuffer,自己操做是加鎖的,若是隻在局部使用不存在併發訪問,那麼會擦除鎖,若是對象逃逸出去例如賦值給全局變量等,面臨併發訪問,就不會擦除鎖。能夠經過jvm參數來指定是否使用鎖消除。

3 jdk的鎖優化  sychronized的優化,由虛擬機完成 

  (1)偏向鎖  在競爭比較少的狀況下,會使用偏向鎖來提升性能。

       *對象頭 markword,共32位,存hash,鎖信息(指向鎖的指針),垃圾回收標誌(偏向鎖id),年齡信息,偏向鎖線程id,monitor信息等。

 

  一個線程爭取到對象資源時,對象會在對象頭中標記爲偏向,而且將線程id寫入到對象頭中,下次若是這個線程再來能夠不經過鎖競爭直接進入同步塊。當其餘線程訪問的時候,偏向結束,升級爲輕量級鎖。因此在競爭激烈的場景下偏向鎖會增長系統負擔,jvm默認是開啓偏向鎖的,能夠經過jvm參數設置取消偏向鎖   

      *偏向鎖只須要在置換ThreadID的時候依賴一次CAS原子指令,在只有一個線程執行同步塊時進一步提升性能。

  (2)輕量級鎖 輕量級鎖所適應的場景是線程交替執行同步塊的狀況,若是存在同一時間訪問同一鎖的狀況,就會致使輕量級鎖膨脹爲重量級鎖。

   輕量級鎖的加鎖過程 :

      1)在代碼進入同步塊的時候,若是同步對象鎖狀態爲無鎖狀態(偏向鎖也是無鎖),虛擬機首先將在當前線程的棧幀中創建一個名爲鎖記錄(Lock Record)的空間,用於存儲鎖對象目前的Mark Word的拷貝。

      2)拷貝對象頭中的Mark Word複製到鎖記錄中。

      3)拷貝成功後,虛擬機將使用CAS操做嘗試將對象的Mark Word更新爲指向Lock Record的指針,並將Lock record裏的owner指針指向object mark word。

      4)若是這個更新動做成功了,那麼這個線程就擁有了該對象的鎖,而且對象Mark Word的鎖標誌位設置爲處於輕量級鎖定狀態。

     5)若是這個更新操做失敗了,虛擬機首先會檢查對象的Mark Word是否指向當前線程的棧幀,若是是就說明當前線程已經擁有了這個對象的鎖,那就能夠直接進入同步塊繼續執行。不然說明多個線程競爭鎖,輕量級鎖就要膨脹爲重量級鎖,鎖標誌的狀態值變爲「10」,Mark Word中存儲的就是指向重量級鎖(互斥量)的指針,後面等待鎖的線程也要進入阻塞狀態。 而當前線程便嘗試使用自旋來獲取鎖,自旋就是爲了避免讓線程阻塞,而採用循環去獲取鎖的過程。

      輕量級鎖解鎖時,把複製的對象頭替換回去(cas)若是替換成功,鎖結束,若是失敗,說明有競爭,升級爲重量級鎖(先會自旋一下等等看),notify 喚醒其餘等待線程。

  * 輕量級鎖是爲了在線程交替執行同步塊時提升性能。         

  (3)自旋鎖

          輕量級鎖加鎖失敗之後,可能先自旋一段時間,嘗試得到輕量級鎖,不會着急升級爲重量級鎖掛起。若是自旋過多,會形成cpu資源浪費,JDK採用了適應性自旋,簡單來講就是一開始設置固定自旋次數,線程若是自旋成功了,則下次自旋的次數會更多,若是自旋失敗了,則自旋的次數就會減小。

       *自旋若是成功,能夠省略線程掛起的時間。jdk7之後默認使用。

3 jdk8新特性

 (1)LongAdder  相似automicLong, 可是提供了「熱點分離」。過程以下:若是併發不激烈,則與automicLong 同樣,cas賦值。若是出現併發操做,則使用數組,數組的各元素之和爲真實value,讓操做分散在數組各個元素上,把併發操做壓力分散,一遇到併發就擴容數組,最後達到高效率。通常cas若是遇到高併發,可能一直賦值失敗致使不斷循環,熱點分離能夠解決這個問題。有點相似concurrenthashmap,分而治之。

 (2)completableFuture 對Future進行加強,支持函數式編程的流式調用。提供更多功能,壓縮編碼量。

 (3)stampedLock 改進讀寫鎖,讀不阻塞寫。若是讀的時候,發生了寫,應該從新讀,不是阻塞寫。解決了通常讀寫鎖讀太多致使寫一直阻塞的問題,讀線程發現數據不一致時觸發從新讀操做。 原理是維護了一個stamp標記,在添加寫鎖的釋放寫鎖的時候,stamp都會改變(好比++),代碼在加讀鎖的時候,能夠先獲得stamp,讀完數據釋放讀鎖的時候,調用validate方法,檢驗剛纔stamp和如今stamp是否相同,若是相同,說明讀的過程當中沒有修改,讀取成功,若是不相同,則說明讀的時候發生了寫,那麼接下來兩種策略,一個是繼續用當前stamp爲初試,繼續讀,讀完比較stamp,是樂觀的辦法;另外一種直接調用readlock(),升級爲正常的讀鎖,是悲觀辦法。

相關文章
相關標籤/搜索