• AQS2:可重入和阻塞


    本文仅基于可重入的锁(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 201782 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()) {
    
                    }
                }
            }
        }
  • 相关阅读:
    typedef和define的详细区别
    谈谈Android Activity的生命周期管理
    【Android面试】Android面试题集锦 (陆续更新)(最新2012618)
    [ZZ]Ubuntu<>Windows 远程桌面连接(debian等同)
    C语言中的全局变量内存分配和初始化顺序
    [刘未鹏]怎样花两年时间去面试一个人
    线程间通信常用的三种方法
    C语言const详解
    细雨寒风水冰 no
    C#读取*.sql文件,并执行里面的SQL语句 no
  • 原文地址:https://www.cnblogs.com/faunjoe88/p/8269313.html
Copyright © 2020-2023  润新知