鎖作為並發共享數據,保證一致性的工具,在JAVA平台有多種實現(如synchronized 和ReentrantLock等等) 。這些已經寫好提供的鎖為我們開發提供了便利,但是鎖的具體性質以及類型卻很少被提及。本系列文章將分析JAVA下常見的鎖名稱以及特性,為大家答疑解惑。
1、自旋鎖
自旋鎖是採用讓當前執行緒不停地的在循環體內執行實現的,當循環的條件被其他執行緒改變時才能進入臨界區。如下複製程式碼代碼如下:
public class SpinLock {
private AtomicReference<Thread> sign =new AtomicReference<>();
public void lock(){
Thread current = Thread.currentThread();
while(!sign .compareAndSet(null, current)){
}
}
public void unlock (){
Thread current = Thread.currentThread();
sign .compareAndSet(current, null);
}
}
使用了CAS原子操作,lock函數將owner設定為當前線程,並且預測原來的值為空。 unlock函數將owner設為null,並且預測值為目前執行緒。
當有第二個執行緒呼叫lock操作時由於owner值不為空,導致迴圈一直被執行,直到第一個執行緒呼叫unlock函數將owner設為null,第二個執行緒才能進入臨界區。
由於自旋鎖只是將當前執行緒不停地執行循環體,不進行執行緒狀態的改變,所以響應速度更快。但當執行緒數不停增加時,效能下降明顯,因為每個執行緒都需要執行,佔用CPU時間。如果線程競爭不激烈,並且保持鎖的時間段。適合使用自旋鎖。
註:此範例為非公平鎖,取得鎖的先後順序,不會依照進入lock的先後順序進行。
2.自旋鎖的其他種類
上文我們講到了自旋鎖,在自旋鎖中另有三種常見的鎖形式:TicketLock ,CLHlock 和MCSlock
Ticket鎖主要解決的是訪問順序的問題,主要的問題是在多核心cpu上:
複製代碼代碼如下:
package com.alipay.titan.dcc.dal.entity;
import java.util.concurrent.atomic.AtomicInteger;
public class TicketLock {
private AtomicInteger serviceNum = new AtomicInteger();
private AtomicInteger ticketNum = new AtomicInteger();
private static final ThreadLocal<Integer> LOCAL = new ThreadLocal<Integer>();
public void lock() {
int myticket = ticketNum.getAndIncrement();
LOCAL.set(myticket);
while (myticket != serviceNum.get()) {
}
}
public void unlock() {
int myticket = LOCAL.get();
serviceNum.compareAndSet(myticket, myticket + 1);
}
}
每次都要查詢一個serviceNum 服務號,影響效能(必須到主記憶體讀取,並阻止其他cpu修改)。
CLHLock 和MCSLock 則是兩種類似類型的公平鎖,以鍊錶的形式進行排序。
複製代碼代碼如下:
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class CLHLock {
public static class CLHNode {
private volatile boolean isLocked = true;
}
@SuppressWarnings("unused")
private volatile CLHNode tail;
private static final ThreadLocal<CLHNode> LOCAL = new ThreadLocal<CLHNode>();
private static final AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER = AtomicReferenceFieldUpdater.newUpdater(CLHLock.class,
CLHNode.class, "tail");
public void lock() {
CLHNode node = new CLHNode();
LOCAL.set(node);
CLHNode preNode = UPDATER.getAndSet(this, node);
if (preNode != null) {
while (preNode.isLocked) {
}
preNode = null;
LOCAL.set(node);
}
}
public void unlock() {
CLHNode node = LOCAL.get();
if (!UPDATER.compareAndSet(this, node, null)) {
node.isLocked = false;
}
node = null;
}
}
CLHlock是不停的查詢前驅變量, 導致不適合在NUMA 架構下使用(在這種結構下,每個執行緒分佈在不同的物理記憶體區域)
MCSLock則是將本地變數的節點循環。不存在CLHlock 的問題。
複製代碼代碼如下:
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class MCSLock {
public static class MCSNode {
volatile MCSNode next;
volatile boolean isLocked = true;
}
private static final ThreadLocal<MCSNode> NODE = new ThreadLocal<MCSNode>();
@SuppressWarnings("unused")
private volatile MCSNode queue;
private static final AtomicReferenceFieldUpdater<MCSLock, MCSNode> UPDATER = AtomicReferenceFieldUpdater.newUpdater(MCSLock.class,
MCSNode.class, "queue");
public void lock() {
MCSNode currentNode = new MCSNode();
NODE.set(currentNode);
MCSNode preNode = UPDATER.getAndSet(this, currentNode);
if (preNode != null) {
preNode.next = currentNode;
while (currentNode.isLocked) {
}
}
}
public void unlock() {
MCSNode currentNode = NODE.get();
if (currentNode.next == null) {
if (UPDATER.compareAndSet(this, currentNode, null)) {
} else {
while (currentNode.next == null) {
}
}
} else {
currentNode.next.isLocked = false;
currentNode.next = null;
}
}
}
從程式碼來看,CLH 要比MCS 更簡單,
CLH 的隊列是隱式的隊列,沒有真實的後繼結點屬性。
MCS 的佇列是明確的佇列,有真實的後繼結點屬性。
JUC ReentrantLock 預設內部使用的鎖就是CLH鎖(有許多改進的地方,將自旋鎖換成了阻塞鎖等等)。
(全文完)