CLH锁的原理和实现

public class CLHLockV2 {

    /**
     *
     * 隐式节点类
     * 作用:维持一个节点状态的字段。一个线程会对应一个节点
     */
    private static class CLHNodeV2 {

        /**
         * 默认状态为true - 即处于等待状态或者加锁成功(换言之,即此节点处于有效的一种状态)
         */
        volatile boolean active = true;

    }

    /**
     * 隐式链表的尾节点
     */
    private volatile CLHNodeV2 tail = null;

    /**
     * 保存当前线程对应的隐式节点
     */
    private ThreadLocal currentThreadNode = new ThreadLocal<>();

    /**
     * 原子更新器
     *
     * 字段参数说明:
     * 包含该字段的对象的类
     * 将被更新的对象的类
     * 将被更新的字段的名称
     *
     * 作用:保证更改参数为原子操作。getAndSet方法使得节点类CLHNodeV2之间有一个隐式的前驱指针。后面详细说明。
     */
    private static final AtomicReferenceFieldUpdater UPDATER = AtomicReferenceFieldUpdater
            .newUpdater(
                    CLHLockV2.class,
                    CLHNodeV2.class,
                    "tail");

    /**
     * 加锁
     */
    public void lock() {

        CLHNodeV2 cNode = currentThreadNode.get();
        //如果当前线程没有设定节点就设定一个
        if (cNode == null) {
            cNode = new CLHNodeV2();
            currentThreadNode.set(cNode);
        }

        //通过getAndSet方法实现了隐式链表,(各个节点之间好像有一个前驱指针指向前一个节点,其实没有),
        //它是通过getAndSet这个方法的返回值去获取当前CLHLockV2的tail以前指向的节点,也就是当前尾节点的前一个节点。
        //比如现在尾节点是A,现在调用方法getAndSet(this,B);现在tail指向B,方法返回A,如果A不为空的话就去空转,
        // 直到A节点的状态变更(变更在unlock中)。
        //这就是隐式链表形成的原因
        //所以这个算法的锁是在它的前一个节点空转。
        CLHNodeV2 predecessor = (CLHNodeV2) UPDATER.getAndSet(this, cNode);
        if (predecessor != null) {
            // 自旋等待前驱节点状态变更 - unlock中进行变更
            while (predecessor.active) {

            }
        }

        // 没有前驱节点表示可以直接获取到锁,由于默认获取锁状态为true,此时可以什么操作都不执行
        // 能够执行到这里表示已经成功获取到了锁
    }

    /**
     * CLH释放锁
     */
    public void unlock() {

        CLHNodeV2 cNode = currentThreadNode.get();

        // 只有持有锁的线程才能够释放
        if (cNode == null || !cNode.active) {
            return;
        }

        // 移除线程和节点的绑定关系
        currentThreadNode.remove();

        //CAS,比较CLHLockV2的tail字段指向的节点和当前线程绑定的节点是不是一个,如果是则说明所有节点都已经解锁,设定尾节点为null,不需要改变节点状态了。
        //如果不是说明还有其他节点,需要将当前节点的状态改变,让其他线程停止空转,执行任务。
        if (!UPDATER.compareAndSet(this, cNode, null)) {
            // 不仅只有当前线程,还有后续节点线程的情况 - 将当前线程的锁状态置为false,因此其后继节点的lock自旋操作可以退出
            cNode.active = false;
        }

    }

    /**
     * 用例
     *
     * @param args
     */
    public static void main(String[] args) {

        final CLHLockV2 lock = new CLHLockV2();

        for (int i = 1; i <= 10; i++) {
            new Thread(generateTask(lock, String.valueOf(i))).start();
        }

    }

    private static Runnable generateTask(final CLHLockV2 lock, final String taskId) {
        return () -> {
            lock.lock();

            try {
                Thread.sleep(3000);
            } catch (Exception e) {

            }

            System.out.println(String.format("Thread %s Completed", taskId));
            lock.unlock();
        };
    }

}

参考资料:

https://blog.csdn.net/dm_vincent/article/details/79842501

https://huangyunbin.iteye.com/blog/1944153

你可能感兴趣的:(java,并发)