1. 程式人生 > >Java鎖之自旋鎖詳解

Java鎖之自旋鎖詳解

並且 port stat static 進入 屬性 ESS 節點 round

鎖作為並發共享數據,保證一致性的工具,

在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鎖(有很多改進的地方,將自旋鎖換成了阻塞鎖等等)。

(全文完)

Java鎖之自旋鎖詳解