代码改变世界

AQS2:可重入和阻塞

2018-01-11 16:55  faunjoe88  阅读(398)  评论(0编辑  收藏  举报

本文仅基于可重入的锁(ReentrantLock类)对AQS做分析,只考虑独占锁。
共享锁与独占锁的更多信息,以后再讨论。

AQS中队列的实现

节点Node

AQS的节点包含了对前置节点的引用pre,后置节点的引用next,以及持有节点的线程thread.

static final class Node {

    /**
      pre节点,主要用来实现取消
     * Link to predecessor node that current node/thread relies on
     * for checking waitStatus. Assigned during enqueuing, and nulled
     * out (for sake of GC) only upon dequeuing.  Also, upon
     * cancellation of a predecessor, we short-circuit while
     * finding a non-cancelled one, which will always exist
     * because the head node is never cancelled: A node becomes
     * head only as a result of successful acquire. A
     * cancelled thread never succeeds in acquiring, and a thread only
     * cancels itself, not any other node.
     */
    volatile Node prev;

    /**
    next节点,主要用来实现阻塞/唤醒
     * Link to the successor node that the current node/thread
     * unparks upon release. Assigned during enqueuing, adjusted
     * when bypassing cancelled predecessors, and nulled out (for
     * sake of GC) when dequeued.  The enq operation does not
     * assign next field of a predecessor until after attachment,
     * so seeing a null next field does not necessarily mean that
     * node is at end of queue. However, if a next field appears
     * to be null, we can scan prev's from the tail to
     * double-check.  The next field of cancelled nodes is set to
     * point to the node itself instead of null, to make life
     * easier for isOnSyncQueue.
     */
    volatile Node next;

    //Node 节点 关联的线程
    //1.重入时判断是否持有锁的线程
    //2.取消阻塞时,unpark节点对应的线程
    /**
     * The thread that enqueued this node.  Initialized on
     * construction and nulled out after use.
     */
    volatile Thread thread;
}

AQS的属性

CLH队列中只有tail节点,通过tail节点就可以实现首尾相连的队列。
AQS中则使用head和tail节点实现队列。
入队时,通过tail节点构建CLH队列
出队时,只需要设置head节点(把当前成功获取锁的节点设置为head,则head节点为持有锁的节点)

入队:

/**
     * Creates and enqueues node for current thread and given mode.
     *
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return the new node
     */
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        //尝试直接设置tail入队,失败了走完整的enq入队逻辑
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        //注意:如果没有调用过enq tail是为空的,下面的判断不成立
        //也就是说,enq方法调用之后,尝试直接设置tail入队,才有机会
        if (pred != null) {
            node.prev = pred;
            //如果入队成功,直接返回
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //完整的入队逻辑
        enq(node);
        return node;
    }

    /**
        自旋入队(包含了初始化流程)
     * Inserts node into queue, initializing if necessary. See picture above.
     * @param node the node to insert
     * @return node's predecessor
     */
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                //初始化
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                //入队成功则返回,不成功继续自旋,最终生成队列
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

考虑enq不断自旋的情况:

线程1
1.第1次循环
tail为空,初始化
tail=head = new Node(无参构造,空的Node)

                  +------+  
    head(tail)    |      |  
                  +------+ 

没有return,继续循环

2.第2次循环

如果成功入队

        +------+  prev +-----+     
    head |      | <---- |     |  tail(当前节点)
         +------+       +-----+  

返回前置节点,结束自旋

线程2
3.第1次循环

         +------+  prev +-----+       +-----+
    head |      | <---- |     | <---- |     |  tail
         +------+       +-----+       +-----+

可重入

AQS为了支持可重入,使用了计数方式,对应属性是 volatile int state.

//AQS的同步状态
    /**
     * The synchronization state.
     */
    private volatile int state;

①某个线
程获取锁的标志就是原子性的把state从某个期望状态(expect)改为指定状态(target)。
如果修改的时候state!=expect,说明state被其他线程改动了,其语义就是:锁被其他线程持有。
当前线程只能自旋/阻塞,直到持有锁的线程把状态改回expect,当前线程才能结束自旋,并且持有锁。

②支持可重入,则需要计数机制,每次重入,state+1,释放则state-1.

阻塞/唤醒

AQS使用LockSupport来阻塞/唤醒线程。
LockSupport针对每个线程操作,发给每个线程一个许可(permit),与blocker无关。

//如果得到许可,则立即返回,否则阻塞当前线程
    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }   

     //给指定传入的线程许可,解除阻塞
    public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }    

LockSupport不能被滥用。对当前线程调用unpark,可能导致AQS产生不可预知的情况。

park的解除条件

  1. 其他线程对当前线程调用了unpark
  2. 其他线程中断了当前线程
  3. 不可预知的返回

只考虑可重入和阻塞的AQS的简单实现

import com.google.common.collect.Lists;

    import java.util.List;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
    import java.util.concurrent.locks.LockSupport;

    /**
     * Created by 张三丰 on 2017\8\2 0002.
     */
    public class ReentanceAndBlock {

        static class CLHLock {
            static class Node {
                /**
                 * 节点的锁定状态,使用阻塞机制后,不需要这个状态了,获取不到锁的线程被阻塞,并在合适的时机被唤醒
                 */
    //            volatile boolean isLocked = true;
                //下一个节点
                volatile Node next;
                //持有节点的线程
                volatile Thread thread;

                public Node() {
                }

                public Node(Thread thread) {
                    this.thread = thread;
                }
            }

            //可重入的状态,aqs使用了int 类型,然后使用了unsafe做cas操作。
            // 这里不涉及unsafe,所以使用AtomicInteger
            private volatile AtomicInteger state = new AtomicInteger();

            private volatile Node tailNode;

            private volatile Node headNode;

            //这个值只需要持有锁的线程自己可见即可(锁重入时判断,只能是同一个线程)
            // ,不需要volatile (其他线程见不见不影响)
            private Thread holdLockThread;

            private static final AtomicReferenceFieldUpdater<CLHLock, Node> TAIL_UPDATER =
                    AtomicReferenceFieldUpdater.newUpdater(CLHLock.class, Node.class, "tailNode");

            private static final AtomicReferenceFieldUpdater<CLHLock, Node> HEAD_UPDATER =
                    AtomicReferenceFieldUpdater.newUpdater(CLHLock.class, Node.class, "headNode");

            public void lock(int acquire) {
                //1.尝试获取锁
                if (tryLock(acquire)) {
                    return;
                }
                //2.入队
                Node current = null;
                for (; ; ) {
                    Node t = tailNode;
                    if (t == null) {
                        Node node = new Node();
                        if (TAIL_UPDATER.compareAndSet(this, null, node)) {
                            //初始化时,head节点是一个假的节点,绑定的是当前持有锁的线程
                            //以后也会一直保持绑定持有锁的节点
                            headNode = tailNode;
                        }
                    } else {
                        Node node = new Node(Thread.currentThread());
                        if (TAIL_UPDATER.compareAndSet(this, t, node)) {
                            t.next = node;
                            current = node;
                            break;
                        }
                    }
                }
                //3.自旋获取锁,或阻塞后等待唤醒
                for (; ; ) {
                    //头节点是持有锁的节点,检查头节点,竞争锁
                    if (headNode.next == current) {
                        //前置节点是头节点,不能阻塞,一直自旋尝试获取锁
                        //这种情况是为了防止,被唤醒后,恰好有另外一个线程获取了锁,竞争失败的情况
                        if (tryLock(acquire)) {
                            headNode = current;
                            break;
                        }
    //                    System.out.println("自旋获取锁~~");
                    } else {
                        //打印队列数
    //                    System.out.println("====" + __getCount());
                        //阻塞当前线程,等待前置节点唤醒
                        LockSupport.park(this);
                    }
                }
            }

            /**
             * 获取队列数
             *
             * @return
             */
            private int __getCount() {
                int count = 1;
                Node node = headNode.next;
                while (node != null) {
                    count++;
                    node = node.next;
                }
                return count;
            }

            /**
             * 定义获取锁的逻辑,AQS中此类由具体子类实现
             * @param acquire
             * @return
             */
            private boolean tryLock(int acquire) {
                int state = this.state.get();
                if (state == 0 && this.state.compareAndSet(0, acquire)) {
                    //获取锁成功,不需要入队
                    holdLockThread = Thread.currentThread();
    //                __print1();
                    return true;
                } else if (holdLockThread == Thread.currentThread()) {
                    //线程重入
                    this.state.set(this.state.get() + acquire);
                    return true;
                }
                return false;
            }

            private void __print1() {
                if (headNode != null) {
                    Node node = headNode.next;
                    boolean isInCLH = false;
                    while (node != null) {
                        if (node.thread == Thread.currentThread()) {
                            isInCLH = true;
                            break;
                        }
                        node = node.next;
                    }
                    if (!isInCLH) {
                        System.out.println("厉害了,在唤醒的线程之前抢到了锁");
                    }
                }
            }

            /**
             *
             */
            public void unlock(int acquire) {
    //            System.out.println("unlock" + Thread.currentThread());
                if (tryRelease(acquire)) {
                    holdLockThread = null;
                    state.set(0);
                    unparkNext();
                }
            }

            /**
             * 定义释放锁的逻辑,AQS中此类由具体子类实现
             * @param acquire
             * @return
             */
            private boolean tryRelease(int acquire) {
                //只有持有锁的线程才能释放锁
                if (Thread.currentThread() != holdLockThread) {
                    System.out.println("不是hold" + holdLockThread);
                    return false;
                }
                int newValue = state.get() - acquire;
                //不会执行到
                if (newValue < 0) {
                    throw new RuntimeException("state < 0");
                }
                //减去计数
                state.set(newValue);
                return newValue == 0;
            }

            private void unparkNext() {
                Node headNode = this.headNode;
                if (headNode != null) {
                    Node next = headNode.next;
                    if (next != null) {
                        //唤醒下个节点
                        LockSupport.unpark(next.thread);
                    }
                }
            }
        }

        static class CLHTester {
            public static void main(String[] args) throws InterruptedException {
                testLockAndUnlock();
                testReentrance();
            }

            private static void testReentrance() {
                System.out.println("testReentrance");
                final Long[] number = {0L};
                ExecutorService executorService = Executors.newFixedThreadPool(10);
                CLHLock lock = new CLHLock();
                for (int i = 0; i < 100; i++) {
                    executorService.execute(() -> {
                        lock.lock(1);
                        Long n = number[0];
                        if (n == 50) {
                            System.out.println("重入之前计数:" + lock.state);
                            System.out.println("锁重入2");
                            lock.lock(2);
                            System.out.println("重入之后计数:" + lock.state);
                            lock.unlock(2);
                            System.out.println("锁释放2");
                            System.out.println("释放之后计数:" + lock.state);
                        }
                        number[0] = n + 1;
                        System.out.println(number[0]);
                        lock.unlock(1);
                    });
                }
                executorService.shutdown();
            }

            private static void testLockAndUnlock() {
                System.out.println("testLockAndUnlock");
                //测试锁,循环100次,要求多线程共享的值能按照顺序+1,最终得到正确的结果 100
                List<Integer> sharedValue = Lists.newArrayList(new Integer(0));
                ExecutorService executorService = Executors.newFixedThreadPool(50);
                CLHLock lock = new CLHLock();
                for (int i = 0; i < 100; i++) {
                    executorService.execute(() -> {
                        lock.lock(1);
                        //把数据+1
                        sharedValue.add(0, sharedValue.get(0) + 1);
                        //输出的结构必然是按顺序的
                        System.out.println(sharedValue.get(0));
    //                    System.out.println(sharedValue.get(0) + "=========" + Thread.currentThread().toString());
                        lock.unlock(1);
                    });
                }
                executorService.shutdown();
                while (!executorService.isTerminated()) {

                }
            }
        }
    }