有助於提升鎖定效能的幾點建議
#減少鎖定的持有時間
只有在必要的時候進行同步,明顯減少鎖定的持有時間,降低鎖定衝突的可能性,提高並發能力
例如,使用synchronize同步鎖,盡量加到要物件需要共享變數狀態的時候,不是一味的對整個方法前加synchronize,直接給調用這個方法的物件上鎖,增大了鎖競爭的機率
讀寫鎖定分離替換獨佔鎖定
## 之前講過使用ReadWriteLock讀寫分離提高效率,
再引申就是鎖定分離策略
#對獨佔鎖定分離這種技術典型的引用場景就是LinkedBlockingQueue任務隊列,前面我們說過它是無界的任務隊列,基於鍊錶實現,它的take()方法和put()方法分別是作用於隊列的前端和尾端,互補影響,所以在jdk的實作中,為這兩個操作提供了兩把鎖,
那例如多執行緒執行put()操作最需要競爭putLock,take操作競爭takeLock
/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();
/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();
/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();
/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();
登入後複製
鎖定粗化
對於一直不斷申請和釋放同一個資源的鎖可以整合成對鎖的一次性請求,從而減少申請釋放動作的消耗。
例如:
for(int i=0;i<n;i++){
synchronized(lock){}
}
登入後複製
優化為
synchronized(lock){
for(int i=0;i<n;i++){}
}
登入後複製
減小鎖定粒度
這種技術典型的應用場景就是ConcurrentHashMap的實現,相比HashMap,他是線程安全,相較於HashTable它是高效並發
ConcurrentHashMap實作原理
ConcurrentHashMap在jdk1.7和jdk1.8的實作有很大的區別
ConcurrentHashMap底層結構是一個Segment數組,預設大小是16,每個Segment數組又可以看成是一個小的HashMap,也就是說Segment數組使用鍊錶加數組實現的。
-
jdk 1.7的實作基於分段鎖定的ConcurrentHashMap
那例如我們需要給Map插入一個新的鍵值對,首先根據key的hashcode找到它應該插入到哪個段,然後對這一段進行加鎖完成put操作,這裡segment就扮演鎖的作用,因為
Segment 類別繼承於ReentrantLock 類別,取得鎖定時,並不會直接使用lock來獲取,因為該方法取得鎖定失敗時會掛起。事實上,它使用了自旋鎖定,如果tryLock取得鎖定失敗,表示鎖定被其它執行緒佔用,此時透過循環再次以tryLock的方式申請鎖定。那麼在多執行緒中,只要插入的資料定位不在一個段中,也不會發生鎖定競爭導致阻塞,執行緒間就可以做到真正的並發。
問題:當需要進行跨段操作的時候,也就是當系統需要獲得全域鎖的時候,就比較麻煩了,它要查看每個段的鎖情況,只有順利取得全部段的鎖才能獲取全域資訊。例如ConcurrentHashMap的size()方法,就要獲得所有子段的鎖
-
jdk 1.8的實作基於CAS的ConcurrentHashMap
jdk1.7的ConcurrentHashMap最大的並發性與分段的段數相等,jdk 1.8為進一步提高並發性,摒棄了分段鎖的方案,而是直接使用一個大的數組。同時為了提高雜湊碰撞下的尋址效能,Java 8在鍊錶長度超過一定閾值(8)時將鍊錶(尋址時間複雜度為O(N))轉換為紅黑樹(尋址時間複雜度為O(long(N)))。同樣是透過Key的雜湊值與數組長度取模確定該Key在數組中的索引
# 對於put操作,如果Key對應的數組元素為null,則透過CAS操作將其設定為目前值。如果Key對應的陣列元素(也即鍊錶錶頭或樹的根元素)不為null,則對該元素使用synchronized關鍵字申請鎖,然後進行操作。如果該put操作使得目前鍊錶長度超過一定閾值,則將該鍊錶轉換為樹,從而提高定址效率。
對於讀取操作,由於陣列被volatile關鍵字修飾,因此不用擔心陣列的可見性問題。同時每個元素是一個Node實例(Java 7中每個元素是一個HashEntry),它的Key值和hash值都由final修飾,不可變更,無須關心它們被修改後的可見性問題。而其Value及對下一個元素的引用由volatile修飾,可見性也有保障。
size操作:大的陣列每個都有維護一個計數器,put方法和remove方法都會透過addCount方法來維護Map的size。 size方法透過sumCount取得由addCount方法所維護的Map的size。
特別要注意的是,之所以在每個數組中包含一個計數器,而不是在ConcurrentHashMap 中使用全域的計數器,是對ConcurrentHashMap 並發性的考慮:因為這樣當需要更新計數器時,不用鎖定整個ConcurrentHashMap特別需要注意的是,count是volatile的,這使得對count的任何更新對其它線程都是立即可見的
#相關文章:
Java框架Bootstrap、jQuery、SpringMVC、Hibernate高效能高並發
Java系統的高並發問題的解決
相關影片:
Java影片教學
#
以上是學習Java並發:鎖定優化、ConcurrentHashMap、鎖定分離的詳細內容。更多資訊請關注PHP中文網其他相關文章!