克雷格.蘭丁&hagersten (CLH Lock)

CLH Lock摘要

CLH lock is Craig, Landin, and Hagersten (CLH) locks, CLH lock is a spin lock, can ensure no hunger, provide fairness first come first service.
The CLH lock is a scalable, high performance, fairness and spin lock based on the list, the application thread spin only on a local variable, it constantly polling the precursor state, if it is found that the pre release lock end spin.java

CLH鎖是自旋鎖的一種,對它的研究是由於AQS源代碼中使用了CLH鎖的一個變種,爲了更好的理解AQS中使用鎖的思想,因此決定先好好理解CLH鎖node

Java代碼實現

public class ClhSpinLock implements Lock{
    private final ThreadLocal<Node> prev;
    private final ThreadLocal<Node> node;
    private final AtomicReference<Node> tail = new AtomicReference<Node>(new Node());

    public ClhSpinLock() {
        this.node = new ThreadLocal<Node>() {
            protected Node initialValue() {
                return new Node();
            }
        };

        this.prev = new ThreadLocal<Node>() {
            protected Node initialValue() {
                return null;
            }
        };
    }

    /**
     * 1.初始狀態 tail指向一個node(head)節點 
     * +------+ 
     * | head | <---- tail 
     * +------+
     * 
     * 2.lock-thread加入等待隊列: tail指向新的Node,同時Prev指向tail以前指向的節點
     * +----------+
     * | Thread-A |
     * | := Node  | <---- tail
     * | := Prev  | -----> +------+
     * +----------+        | head |
     *                     +------+ 
     * 
     *             +----------+            +----------+
     *             | Thread-B |            | Thread-A |
     * tail ---->  | := Node  |     -->    | := Node  | 
     *             | := Prev  | ----|      | := Prev  | ----->  +------+
     *             +----------+            +----------+         | head |
     *                                                          +------+ 
     * 3.尋找當前node的prev-node而後開始自旋
     * 
     */
    public void lock() {
        final Node node = this.node.get();
        node.locked = true;
        Node pred = this.tail.getAndSet(node);
        this.prev.set(pred);
        // 自旋
        while (pred.locked);
    }

    public void unlock() {
        final Node node = this.node.get();
        node.locked = false;
        this.node.set(this.prev.get());
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        // TODO Auto-generated method stub
        
    }

    @Override
    public boolean tryLock() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public Condition newCondition() {
        // TODO Auto-generated method stub
        return null;
    }
    
    private static class Node {
        private volatile boolean locked;
    }
}

簡單的看一下CLH的算法定義算法

the list, the application thread spin only on a local variable, it constantly polling the precursor state, if it is found that the pre release lock end spin.架構

基於list,線程僅在一個局部變量上自旋,它不斷輪詢前一個節點狀態,若是發現前一個節點釋放鎖結束.併發

因此在java中使用了ThreadLocal做爲具體實現,AtomicReference爲了消除多個線程併發對tail引用Node的影響,核心方法lock()中分爲3個步驟去實現app

  1. 初始狀態 tail指向一個node(head)節點ide

    private final AtomicReference<Node> tail = new AtomicReference<Node>(new Node());
  2. thread加入等待隊列: tail指向新的Node,同時Prev指向tail以前指向的節點,在java代碼中使用了getAndSet即CAS操做使用性能

    Node pred = this.tail.getAndSet(node);
    this.prev.set(pred);
  3. 尋找當前線程對應的node的前驅node而後開始自旋前驅node的status判斷是否能夠獲取lock測試

    while (pred.locked);

同理unlock()方法,獲取當前線程的node,設置lock status,將當前node指向前驅node(這樣操做tail指向的就是前驅node等同於出隊操做).至此CLH Lock的過程就結束了this

測試ClhSpinLock

public class LockTest {
    static int count = 0;
    
    public static void testLock(Lock lock) {
        try {
            lock.lock();
            for (int i = 0; i < 10000000; i++) ++count;
        } finally {
            lock.unlock();
        }
    }
    
    public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
        final ClhSpinLock clh = new ClhSpinLock();
        final CyclicBarrier cb = new CyclicBarrier(10, new Runnable() {
            @Override
            public void run() {
                System.out.println(count);
            }
        });
        
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    testLock(clh);
                    // 這段代碼是非lock比較使用
//                    for (int i = 0; i < 10000000; i++)
//                        count++;
                    try {
                        cb.await();
                    } catch (InterruptedException | BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

測試代碼使用了CyclicBarrier輔助當全部子線程完成後輸出static變量count的值
結果發現輸出的值和預期同樣,CLH Lock完成了獨佔式鎖的功能

小結

CLH Lock是一種比較簡單的自旋鎖算法之一,由於鎖的CAS操做涉及到了硬件的鎖定(鎖總線或者是鎖內存)因此性能和CPU架構也密不可分,該興趣的同窗能夠繼續深刻研究包括MCS鎖等。CLH Lock是獨佔式鎖的一種,而且是不可重入的鎖,這篇文章是對AQS鎖源代碼分析的預熱篇

相關文章
相關標籤/搜索