前言:java 多線程鎖的優(yōu)化一直是難點竹观!如果優(yōu)化的好镐捧,性能會高很多潜索,比如 jdk 里面提供了很多 juc 的類,以及著名框架 Disruptor,netty 也有很多優(yōu)化懂酱,所以本篇文章就大概的講一下我們在高并發(fā)程序設計對于鎖的優(yōu)化竹习,也希望能幫助到每個看到這篇文章的你。
鎖的使用建議
1.減少鎖持有時間
2.減少鎖粒度
3.讀寫鎖替代獨占鎖
4.鎖分離
5.鎖粗化
減少鎖粒度
例如ConcurrentHashMap,內部分為16個segment,加鎖時不會像hashmap一樣全局加鎖,只需要對相應segment加鎖,但是如果需
要計算map所有的大小size(),則需依次獲取所有segment的鎖
減少鎖的持有時間
減少鎖的持有時間有助于降低沖突的可能性,進而提升并發(fā)能力
讀寫鎖替代獨占鎖(ReadWriteLock)
讀多寫少的場景下使用讀寫鎖可以顯著提高系統(tǒng)的并發(fā)能力
鎖分離
在讀寫鎖的前提上再進行升級,對獨占鎖進行分離,如LinkedBlockingQueue,由于是基于鏈表結構,take()和put()分別作用于隊列的前端和尾端.兩者并不沖突,故可以使用takeLock和putLock,從而削弱鎖競爭的可能性
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly(); //take鎖加鎖
try {
while (count.get() == 0) {
notEmpty.await(); //如果無可用數(shù)據(jù)則一直等待,知道put()方法的通知
}
x = dequeue();
c = count.getAndDecrement(); //原子操作-1
if (c > 1)
notEmpty.signal(); //通知其他take方法
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull(); //通知put方法 已有空間
return x;
}
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
// Note: convention in all put/take/etc is to preset local var
// holding count negative to indicate failure unless set.
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
鎖粗化
一連串對同一個鎖不停地進行請求和釋放會被整合成對鎖的一次操作,從而減少對鎖請求同步次數(shù)
for(int i=0;i<100;i++){
synchronized(lock){ //此時把鎖放到循環(huán)外邊去最好
//...
}
}
JVM對鎖的優(yōu)化
1.鎖偏向
2.輕量級鎖
3.自旋鎖
4.鎖消除
鎖偏向
原理:一個線程獲得鎖,則進入偏向模式,當這個線程再次請求鎖時,無需再做任何同步操作
場景:幾乎沒有鎖競爭的場合
操作:競爭激烈時建議關閉. -XX:+UseBiasedLocking可以開啟偏向鎖
輕量級鎖
原理:偏向鎖失敗后,會膨脹為輕量級鎖,對象頭部會嘗試指向持有鎖的線程堆棧內部,來判斷是否持有對象鎖,如果獲得輕量級鎖成功,則進入臨界區(qū),否則表示其他線程搶占到鎖,當前線程膨脹為重量級鎖(在膨脹前可以自旋再去嘗試獲得)
場景:不存在鎖競爭或競爭不激烈
自旋鎖
原理:鎖膨脹后,嘗試自旋,若干次后若仍得不到鎖,轉入重量級鎖,將線程掛起
場景:競爭不激烈,且持有鎖時間較短
-鎖消除
原理:去除不可能存在共享的資源競爭鎖,如某些jdk內部自帶的類
場景:單線程下的加鎖操作會被鎖消除
逃逸分析:觀察某變量是否會逃逸出某個作用域,如果該變量沒有逃逸出該作用域,則虛擬機會把該變量內部的鎖消除掉
操作:-XX:+DoEscapeAnalysis 打開逃逸分析; -XX:+EliminateLocks 打開鎖消除
ThreadLocal
線程的局部變量,僅當前線程可以訪問,故實現(xiàn)線程安全.
.....