Java鎖優(yōu)化
應(yīng)用程序在并發(fā)環(huán)境下會產(chǎn)生很多問題帝璧,通常情況下避除,我們可以通過加鎖來解決多線程對臨界資源的訪問問題逆航。但是加鎖往往會成為系統(tǒng)的瓶頸宠能,因為加鎖和釋放鎖會涉及到與操作系統(tǒng)的交互刃滓,會有很大的性能問題寡润。那么這個時候基于鎖的優(yōu)化手段就顯得很重要了赴邻。
一般情況下书闸,可以從兩個角度進行鎖優(yōu)化:對單個鎖算法的優(yōu)化和對鎖粒度的細分审胸。
1. 單個鎖的優(yōu)化
自旋鎖:
? 非自旋鎖在未獲取鎖的情況會被阻塞亥宿,之后再喚醒嘗試獲得鎖。而JDK的阻塞和喚醒是基于操作系統(tǒng)實現(xiàn)的歹嘹,會有系統(tǒng)資源的開銷箩绍。自旋鎖就是線程不停地循環(huán)嘗試獲得鎖,而不會將自己阻塞尺上,這樣不會浪費系統(tǒng)的資源開銷材蛛,但是會浪費CPU的資源圆到。所有現(xiàn)在的JDK大部分都是先自旋等待,如果自旋等待一段時間之后還沒有獲取到鎖卑吭,就會將當(dāng)前線程阻塞芽淡。
鎖消除:
? 當(dāng)JVM分析代碼時發(fā)現(xiàn)某個方法只被單個線程安全訪問,而且這個方法是同步方法豆赏,那么JVM就會去掉這個方法的鎖挣菲。
單個鎖優(yōu)化的瓶頸:
? 對單個鎖優(yōu)化的效果就像提高單個CPU的處理能力一樣,最終會由于各個方面的限制而達到一個平衡點掷邦,到達這個點之后優(yōu)化單個鎖的對高并發(fā)下面鎖的優(yōu)化效果越來越低白胀。所以將一個鎖進行粒度細分帶來的效果會很明顯,如果一個鎖保護的代碼塊被拆分成兩個鎖來保護抚岗,那么程序的效率就大約能夠提高到2倍或杠,這個比單個鎖的優(yōu)化帶來的效果要明顯很多。常見的鎖粒度細分技術(shù)有:鎖分解和鎖分段
2. 細分鎖粒度
細分鎖粒度的目的是降低競爭鎖的概率宣蔚。
2.1 鎖分解
鎖分解的核心是將無關(guān)的代碼塊向抢,如果在一個方法中有一部分的代碼與鎖無關(guān),一部分的代碼與鎖有關(guān)胚委,那么可以縮小這個鎖的返回挟鸠,這樣鎖操作的代碼塊就會減少,鎖競爭的可能性也會減少
縮小鎖的范圍
縮小鎖的范圍是指盡量只在必要的地方加鎖亩冬,不要擴大加鎖的范圍艘希,就拿單例模式舉例,范圍大的鎖可能將整個方法都加鎖了:
class Singleton {
private Singleton instance;
private Singleton() {
}
// 將整個方法加鎖
public synchronized Singleton getInstance() {
try {
Thread.sleep(1000); //do something
if(null == instance)
instance = new Singleton();
} catch (InterruptedException e) {
e.printStackTrace();
}
return instance;
}
}
優(yōu)化后的鉴未,只將部分代碼加鎖:
class Singleton {
private Singleton instance;
private Singleton() {
}
public Singleton getInstance() {
try {
Thread.sleep(1000); //do something
// 只對部分代碼加鎖
synchronized(this) {
if(null == instance)
instance = new Singleton();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return instance;
}
}
減少鎖的粒度
減少鎖的粒度是指如果一個鎖需要保護多個相互獨立的變量枢冤,那么可以將一個鎖分解為多個鎖鸠姨,并且每個鎖保護一個變量铜秆,這樣就可以減少鎖沖突⊙惹ǎ看一下下面的例子:
class Demo{
private Set<String> allUsers = new HashSet<String>();
private Set<String> allComputers = new HashSet<String>();
//公用一把鎖
public synchronized void addUser(String user){
allUsers.add(user);
}
public synchronized void addComputer(String computer){
allComputers.add(computer);
}
}
縮小鎖的粒度后连茧,將一個鎖拆分為多個:
class Demo{
private Set<String> allUsers = new HashSet<String>();
private Set<String> allComputers = new HashSet<String>();
//分解為兩把鎖
public void addUser(String user){
synchronized (allUsers){
allUsers.add(user);
}
}
public void addComputer(String computer){
synchronized (allComputers){
allComputers.add(computer);
}
}
}
如上的方法把一個鎖分解為2個鎖時候,采用兩個線程時候巍糯,大約能夠使程序的效率提升一倍啸驯。
2.2 鎖分段
鎖分段和縮小鎖的粒度類似,就是將鎖細分的粒度更多祟峦,比如將一個數(shù)組的每個位置當(dāng)做單獨的鎖罚斗。JDK8以前ConcurrentHashMap就使用了鎖分段技術(shù),它將散列數(shù)組分成多個Segment宅楞,每個Segment存儲了實際的數(shù)據(jù)针姿,訪問數(shù)據(jù)的時候只需要對數(shù)據(jù)所在的Segment加鎖就行袱吆。
參考:
Java鎖分解鎖分段技術(shù): http://guochenglai.com/2016/06/04/java-concurrent4-java-subsection-decompose/
ConcurrentHashMap的鎖分段技術(shù):https://blog.csdn.net/yansong_8686/article/details/50664351