LCH       

LCH

CLH是一种基于单向链表(隐式创建)的高性能、公平的自旋锁,申请加锁的线程只需要在其前驱节点的本地变量上自旋,从而极大地减少了不必要的处理器缓存同步的次数,降低了总线和内存的开销。

public class CLHLock {

    class Node {
        //false代表没人占用锁
        volatile boolean locked = false;
    }

    //指向最后加入的线程
    final AtomicReference<Node> tail = new AtomicReference<>(new Node());
    //使用ThreadLocal保证每个线程副本内都有一个Node对象
    final ThreadLocal<Node> current;


    public CLHLock() {
        //初始化当前节点的node
        current = new ThreadLocal<Node>() {
            @Override
            protected Node initialValue() {
                return new Node();
            }
        };
    }

    public void lock() throws InterruptedException {
        //得到当前线程的Node节点
        Node own = current.get();
        //修改为true,代表当前线程需要获取锁
        own.locked = true;
        //设置当前线程去注册锁,注意在多线程下环境下,这个
        //方法仍然能保持原子性,,并返回上一次的加锁节点(前驱节点)
        Node preNode = tail.getAndSet(own);

        //在前驱节点上自旋
        while (preNode.locked) {
            System.out.println(Thread.currentThread().getName() + " 开始自旋....  ");
            Thread.sleep(2000);
        }
    }

    public void unlock() {
        //当前线程如果释放锁,只要将占用状态改为false即可
        //因为其他的线程会轮询自己,所以volatile布尔变量改变之后
        //会保证下一个线程能立即看到变化,从而得到锁
        current.get().locked = false;
    }

    public static void main(String[] args) throws InterruptedException {

        CLHLock lock = new CLHLock();

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    System.out.println(Thread.currentThread().getName() + "  获得锁 ");
                    //前驱释放,do own work
                    Thread.sleep(5000);
                    System.out.println(Thread.currentThread().getName() + "  释放锁 ");
                    lock.unlock();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Thread t1 = new Thread(runnable, "线程1");
        Thread t2 = new Thread(runnable, "线程2");
        Thread t3 = new Thread(runnable, "线程3");

        t1.start();
        t2.start();
        t3.start();
    }
}

image-20191229094247615

aqs

java的juc中的aqs

  1. 队列头节点释放锁之后,会唤醒下一个节点的线程

  2. 下一个节点检查前一个节点是头节点,则获取锁成功,否则线程挂起

在获取同步状态时,同步器维护一个同步队列,获取状态失败的线程都会被加入到队列中并在队列中进行自旋;移出队列(或停止自旋)的条件是前驱节点为头节点且成功获取了同步状态。在释放同步状态时,同步器调用tryRelease(int arg)方法释放同步状态,然后唤醒头节点的后继节点

// 入队列
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
          	// 如果head是null,设置为new Node()
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
          	// 如果前节点是头节点并获取同步状态成功则获得锁
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 获取失败则阻塞
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

reference

https://cloud.tencent.com/developer/article/1187386