性能與可伸縮性

一、對性能的思考

  思考: 使用多線程必定能提升程序的性能嗎?
  要想經過併發來得到更好的性能,須要作好兩件事;更有效的利用現有的處理資源,以及在出現新的處理資源時使程序儘量利用這些資源。緩存

1.1性能與可伸縮性

  應用程序的性能能夠採用多個指標來衡量,例如服務時間、延遲時間、吞吐率、效率、可伸縮性以及容量,有衡量運行速度的,即多快能完成,又衡量處理能力的,能完成多少工做。多快和多少是相互獨立的,有時候是相互矛盾的。如程序的三層模型,把表現層、業務層和持久層融合到一個單元。性能確定要高於將應用程序分爲多層並將不一樣層次分不到多個系統時的性能。然而,當單一系統達到自身處理能力極限時,再進一步提高處理能力將會很困難。
  可伸縮性是指當增長計算資源(cpu、內存、存儲容量或者I/O帶寬)時,程序的吞吐量或者處理能力能相應的增長。
  一般會接受執行更長的時間或消耗更多的計算資源,以換取應用程序能處理更高的負載。性能優化

1.2評估各類性能權衡因素

性能優化的前提要保證程序的正確運行。大多數性能決策中都包含有多個變量, 有時候會增長某種形式的成原本下降另外一種形式的開銷。對優化措施要進行完全的思考和分析,由於對性能的提高多是併發錯誤最大的來源。對性能調優時,要明確需求,以測試爲基準。bash

2.Amdahl定律

在增長計算資源的狀況下,程序在理論上可以實現的最高加速比,取決於程序中可並行組件與串行組件所佔的比重。F是串行部分,在包含N個處理器的機器中最高加速比爲:
數據結構


當N趨近於無窮大時,最大的加速比趨近於1/F。從下圖能夠看出串行部分對吞吐率的影響。

3.線程引入的開銷

3.1 上下文切換

  若是可運行的線程數大於cpu的數量,那麼操做系統最終會將某個正在運行的線程調度出來,從而使其餘線程可以使用cpu, 這就致使一次上下文切換。上下文切換存在必定的開銷,在調度過程當中須要訪問有操做系統和JVM共享的數據結構cpu時鐘週期,jvm和操做系統消耗的cpu週期越多,應用程序可用的越少。當一個新的線程被切換進來,他所須要的數據可能不在當前處理器的本地緩存中,這致使更加緩慢。
  上下文切換頻繁,將下降吞吐量,unix系統vmstat命令可查看上下文切換次數及內核中執行時間所佔的比例等。若是內核佔用超過10%,極可能是I/O或競爭鎖致使的阻塞。多線程

3.2內存同步

  synchronized和volatile在可見性保證中將抑制編譯器的優化操做,禁止指令重排序。不管是有競爭同步仍是無競爭同步將會消耗一部分的cpu的時鐘週期。現代jvm能經過優化去掉一些不會發生競爭的鎖,所以咱們要將重點放在鎖競爭的地方。併發

synchronized (new Object()) {
         //...
     }
複製代碼

  一些完備的JVM可以經過逸出分析去掉鎖。編譯器也能夠執行鎖力度的粗化操做。若是引用是線程本地的,下面的程序經過逸出分析將去掉四次鎖獲取操做。編譯器會把三個add和1個toString合併爲單個鎖的獲取和釋放操做。jvm

public String getStoogeNames (){
         List<String> stooges = new vector<String>();
         stooges.add("Moe");
         stooges.add("Larry");
         stooges.add("Curly");
         return stooges.toString();
     }    
複製代碼

3.3 阻塞

  對於競爭的同步,jvm在實現阻塞的時候能夠採用自旋等待和線程掛起兩種方式,若是等待時間較長就線程掛起,若是時間短就自旋。對自旋的優化是自適應自旋,根據歷史等待時間肯定是否自旋,如今大多數是採用線程掛起。阻塞時將包含兩次額外的上下文切換。性能

4 減小鎖的競爭

有三種方式能夠下降鎖的競爭程度:測試

  • 減小鎖的持有時間
  • 下降鎖的請求頻率
  • 使用帶有協調機制的獨佔鎖

4.1 縮小鎖的範圍

儘量縮短鎖的持有時間,能夠將與鎖無關的代碼移出同步代碼塊,好比將synchronized方法改成synchronized代碼塊。優化

4.2 減少鎖的粒度

  若是一個鎖須要保護多個相互獨立的狀態變量,能夠將這個鎖分解爲多個鎖,而且每一個鎖只保護一個變量,從而下降每一個鎖被請求的頻率。鎖分解是把競爭的鎖轉化爲非競爭的鎖,從而提升性能和可伸縮性

public class ServerStatus {
         public final Set<String> users;
         public final Set<String> users;
         ...
         public synchronized void addUser(String u){users.add(u);}
         public synchronized void addQuery(String q) {queries.add(q);}
         public synchronized void removeUser(String u){
             users.remove(u);
         }
         public synchronized void removeQuery(String q){
             queries.remove(q);
         }
     }
複製代碼

將ServerStatus從新改寫爲使用鎖分解技術:

public class ServerStatus {
         public final Set<String> users;
         public final Set<String> users;
         ...
         public void addUser(String u){
             synchronized (users) {
                 users.add(u)
             }
         }
         public void addQuery(String q) {
             synchronized (queries) {
                 queries.add(q);
             }
         }
}
複製代碼

4.3 鎖分段

將鎖分解技術進一步擴展爲對一組獨立對象上的鎖進行分解,這種狀況稱爲鎖分段。好比ConcurrentHashMap。

4.4 避免熱點域

每一個操做都請求多個變量時,鎖的粒度將很難下降,將一些結果緩存起來會引入熱點域。好比hashmap中size方法,每一個元素的變化操做都須要訪問它。currenthashmap就進行了相應的優化,不是維護一個全局的技術,而是將每一個分段中的數量相加。

4.5 替代獨佔鎖

另外一種下降競爭鎖影響的技術就是放棄使用獨佔鎖, 可使用ReadWriteLock, 原子變量。

4.6 監測cpu使用率

當測試伸縮性的時候,要保證處理器獲得充分的使用。若是cpu沒有充分利用,一般有一下緣由:

  • 負載不充足
  • I/O密集
  • 外部限制
  • 鎖競爭
相關文章
相關標籤/搜索