自旋鎖(Spin lock)
自旋鎖是指當一個線程嘗試獲取某個鎖時,若是該鎖已被其餘線程佔用,就一直循環檢測鎖是否被釋放,而不是進入線程掛起或睡眠狀態。php
自旋鎖適用於鎖保護的臨界區很小的狀況,臨界區很小的話,鎖佔用的時間就很短。java
簡單的實現
import java.util.concurrent.atomic.AtomicReference; public class SpinLock { private AtomicReference<Thread> owner = new AtomicReference<Thread>(); public void lock() { Thread currentThread = Thread.currentThread(); // 若是鎖未被佔用,則設置當前線程爲鎖的擁有者 while (owner.compareAndSet(null, currentThread)) { } } public void unlock() { Thread currentThread = Thread.currentThread(); // 只有鎖的擁有者才能釋放鎖 owner.compareAndSet(currentThread, null); } }
SimpleSpinLock裏有一個owner屬性持有鎖當前擁有者的線程的引用,若是該引用爲null,則表示鎖未被佔用,不爲null則被佔用。緩存
這裏用AtomicReference是爲了使用它的原子性的compareAndSet方法(CAS操做),解決了多線程併發操做致使數據不一致的問題,確保其餘線程能夠看到鎖的真實狀態。
多線程
缺點
- CAS操做須要硬件的配合;
- 保證各個CPU的緩存(L一、L二、L三、跨CPU Socket、主存)的數據一致性,通信開銷很大,在多處理器系統上更嚴重;
- 無法保證公平性,不保證等待進程/線程按照FIFO順序得到鎖。
Ticket Lock
Ticket Lock 是爲了解決上面的公平性問題,相似於現實中銀行櫃檯的排隊叫號:鎖擁有一個服務號,表示正在服務的線程,還有一個排隊號;每一個線程嘗試獲取鎖以前先拿一個排隊號,而後不斷輪詢鎖的當前服務號是不是本身的排隊號,若是是,則表示本身擁有了鎖,不是則繼續輪詢。併發
當線程釋放鎖時,將服務號加1,這樣下一個線程看到這個變化,就退出自旋。性能
簡單的實現
import java.util.concurrent.atomic.AtomicInteger; public class TicketLock { private AtomicInteger serviceNum = new AtomicInteger(); // 服務號 private AtomicInteger ticketNum = new AtomicInteger(); // 排隊號 public int lock() { // 首先原子性地得到一個排隊號 int myTicketNum = ticketNum.getAndIncrement(); // 只要當前服務號不是本身的就不斷輪詢 while (serviceNum.get() != myTicketNum) { } return myTicketNum; } public void unlock(int myTicket) { // 只有當前線程擁有者才能釋放鎖 int next = myTicket + 1; serviceNum.compareAndSet(myTicket, next); } }
缺點
Ticket Lock 雖然解決了公平性的問題,可是多處理器系統上,每一個進程/線程佔用的處理器都在讀寫同一個變量serviceNum ,每次讀寫操做都必須在多個處理器緩存之間進行緩存同步,這會致使繁重的系統總線和內存的流量,大大下降系統總體的性能。this
下面介紹的CLH鎖和MCS鎖都是爲了解決這個問題的。atom
MCS 來自於其發明人名字的首字母: John Mellor-Crummey和Michael Scott。spa
CLH的發明人是:Craig,Landin and Hagersten。.net
CLH鎖
CLH鎖也是一種基於鏈表的可擴展、高性能、公平的自旋鎖,申請線程只在本地變量上自旋,它不斷輪詢前驅的狀態,若是發現前驅釋放了鎖就結束自旋。
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; public class CLHLock { public static class CLHNode { private boolean isLocked = true; // 默認是在等待鎖 } @SuppressWarnings("unused" ) private volatile CLHNode tail ; private static final AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER = AtomicReferenceFieldUpdater . newUpdater(CLHLock.class, CLHNode .class , "tail" ); public void lock(CLHNode currentThreadCLHNode) { CLHNode preNode = UPDATER.getAndSet( this, currentThreadCLHNode); // 轉載人註釋: 把this裏的"tail" 值設置成currentThreadCLHNode if(preNode != null) {//已有線程佔用了鎖,進入自旋 while(preNode.isLocked ) { } } } public void unlock(CLHNode currentThreadCLHNode) { // 若是隊列裏只有當前線程,則釋放對當前線程的引用(for GC)。 if (!UPDATER .compareAndSet(this, currentThreadCLHNode, null)) { // 還有後續線程 currentThreadCLHNode. isLocked = false ;// 改變狀態,讓後續線程結束自旋 } } }
MCS鎖
MCS Spinlock 是一種基於鏈表的可擴展、高性能、公平的自旋鎖,申請線程只在本地變量上自旋,直接前驅負責通知其結束自旋,從而極大地減小了沒必要要的處理器緩存同步的次數,下降了總線和內存的開銷。
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; public class MCSLock { public static class MCSNode { MCSNode next; boolean isLocked = true; // 默認是在等待鎖 } volatile MCSNode queue ;// 指向最後一個申請鎖的MCSNode private static final AtomicReferenceFieldUpdater<MCSLock, MCSNode> UPDATER = AtomicReferenceFieldUpdater . newUpdater(MCSLock.class, MCSNode. class, "queue" ); public void lock(MCSNode currentThreadMcsNode) { MCSNode predecessor = UPDATER.getAndSet(this, currentThreadMcsNode);// step 1 if (predecessor != null) { predecessor.next = currentThreadMcsNode;// step 2 while (currentThreadMcsNode.isLocked ) {// step 3 } } } public void unlock(MCSNode currentThreadMcsNode) { if ( UPDATER.get( this ) == currentThreadMcsNode) {// 鎖擁有者進行釋放鎖纔有意義 if (currentThread.next == null) {// 檢查是否有人排在本身後面 if (UPDATER.compareAndSet(this, currentThreadMcsNode, null)) {// step 4 // compareAndSet返回true表示確實沒有人排在本身後面 return; } else { // 忽然有人排在本身後面了,可能還不知道是誰,下面是等待後續者 // 這裏之因此要忙等是由於:step 1執行完後,step 2可能還沒執行完 while (currentThreadMcsNode.next == null) { // step 5 } } } currentThreadMcsNode.next.isLocked = false; currentThreadMcsNode.next = null;// for GC } } }
差別:
- 從代碼實現來看,CLH比MCS要簡單得多。
- 從自旋的條件來看,CLH是在前驅節點的屬性上自旋,而MCS是在本地屬性變量上自旋
- 從鏈表隊列來看,CLH的隊列是隱式的,CLHNode並不實際持有下一個節點;MCS的隊列是物理存在的。
- CLH鎖釋放時只須要改變本身的屬性,MCS鎖釋放則須要改變後繼節點的屬性。
注意:這裏實現的鎖都是獨佔的,且不能重入的。