zoukankan      html  css  js  c++  java
  • (锁) 系列篇 -- "3.2、通过独占锁ReentrantLock理解AQS"

    (锁) 系列篇

    一、总体思路
    二、ReentrantLock#lock方法获取锁为入口
    三、release释放锁

    (以open-jdk 1.8.0为源码分析版本)

    lock获锁

    一、总体思路

    AQS未使用原生Synchronized机制支持,在获锁的过程必须自我实现获锁、释放锁、线程阻塞、线程唤醒等功能。利用CLH虚拟的双向队列结构,
    在未获锁情况下线程封装为队列节点入列阻塞等待,释放锁时候唤醒等待节点

    • CLH (Craig, Landin, and Hagersten)是一个虚拟的双向队列结构,AQS中只是保留了头部(head)和尾部(tail)

    CLH头部和尾部节点

       /**
         * Head of the wait queue, lazily initialized.  Except for
         * initialization, it is modified only via method setHead.  Note:
         * If head exists, its waitStatus is guaranteed not to be
         * CANCELLED.
         */
        private transient volatile Node head;
        /**
         * Tail of the wait queue, lazily initialized.  Modified only via
         * method enq to add new wait node.
         */
        private transient volatile Node tail;
    
    • CLH虚拟队列结构如下图:带头(dummy head)双向链表节点

    • volatile修饰的整形变量state标识锁的状态:state可大于1,以此来实现锁可重入(即获锁的线程允许再次获锁)

       /**
         * 同步锁状态
         */
        private volatile int state;
        protected final int getState() {
            return state;
        }
        protected final void setState(int newState) {
            state = newState;
        }
        /**
         * cas原子性更新state
         */
        protected final boolean compareAndSetState(int expect, int update) {
            return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
        }
    

    源码分析
    (1)、以ReentrantLock#lock方法获取锁为入口

     public void lock() {
        sync.lock();
     }
    

    (2)、sync为内部变量,构造ReentrantLock时根据参数创建公平锁和非公平锁,空参构造默认创建非公平锁。

    /** Synchronizer providing all implementation mechanics */
        private final Sync sync; 
       /**
         * Creates an instance of {@code ReentrantLock}.
         * This is equivalent to using {@code ReentrantLock(false)}.
         */
        public ReentrantLock() {
            sync = new NonfairSync();
        }
        /**
         * Creates an instance of {@code ReentrantLock} with the
         * given fairness policy.
         *
         * @param fair {@code true} if this lock should use a fair ordering policy
         */
        public ReentrantLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
        }
    

    (3)、Sync抽象类继承自AbstractQueuedSynchronizer(AQS),NonFairSync和FairSync都继承自Sync实现非公平锁和公平锁机制。

    /**
        * Base of synchronization control for this lock. Subclassed
        * into fair and nonfair versions below. Uses AQS state to
        * represent the number of holds on the lock.
        */
        abstract static class Sync extends AbstractQueuedSynchronizer {
            final void lock() {
                acquire(1);
            }
            // ......
        }
       /**
         * Sync object for non-fair locks
         */
        static final class NonfairSync extends Sync {
           //......
        }
        /**
         * Sync object for fair locks
         */
        static final class FairSync extends Sync {
            // ......
        }
    

    (4)、先以FairSync为例,FairSync最终将调用AbstractQueuedSynchronizer#acquire(int arg)获锁
    (其中arg参数即为获取锁的数量,要完全释放锁则获取多少数量,释放锁时必须释放对应数量)

    public final void acquire(int arg) {
            if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                selfInterrupt();
    }
    

    依次分析核心方法:

    • tryAcquire(int arg):尝试获锁
    • addWaiter(Node node):节点进入CLH等待队列
    • acquireQueued(Node node, int arg):节点是否可以获锁,获取不到即阻塞等待
    • selfInterrupt():自我产生中断

    (4.1)、tryAcquire实现在FairSync内部。

    /**
         * Sync object for fair locks
         */
        static final class FairSync extends Sync {
            private static final long serialVersionUID = -3000897897090466540L;
    
            final void lock() {
                acquire(1);
            }
            /**
             * Fair version of tryAcquire.  Don't grant access unless
             * recursive call or no waiters or is first.
             */
            protected final boolean tryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                // 获取锁状态 0表示锁无人占用
                int c = getState();
                if (c == 0) {
                    // 此时无前继节点即代表当前节点在队列头部,则利用cas原子获锁
                    if (!hasQueuedPredecessors() &&
                        compareAndSetState(0, acquires)) {
                        // 利用AbstractOwnableSynchronizer提供的基础支持,设置当前线程为锁拥有者
                        setExclusiveOwnerThread(current);
                        return true;
                    }
                }
                // 锁可重入:锁拥有者可多次获取锁
                else if (current == getExclusiveOwnerThread()) {
                    int nextc = c + acquires;
                    if (nextc < 0)
                        throw new Error("Maximum lock count exceeded");
                    setState(nextc);
                    return true;
                }
                return false;
            }
        }
    

    (4.2)、再来看看实现 AbstractQueuedSynchronizer#addWaiter(Node node) 节点入列的过程:

    • CLH双向虚拟队列的节点Node类
    static final class Node {
            /** 标识为分享模式 */
            static final Node SHARED = new Node();
            /** 标识为独占模式 */
            static final Node EXCLUSIVE = null;
    
            /** 节点状态 0不表示属于以下任务状态 */
            /** 表示当前节点已取消等待锁 */
            static final int CANCELLED =  1;
            /** 表示当前节点需要唤醒状态,同时"后继节点"需要被阻塞 */
            static final int SIGNAL    = -1;
            /** 表示当前节点在等待Condition唤醒 */
            static final int CONDITION = -2;
            /** 表示其它线程获取到“共享锁”,对应的waitStatus的值 */
            static final int PROPAGATE = -3;
            volatile int waitStatus;
            /**
             * 前继节点
             */
            volatile Node prev;
            /**
             * 后继节点
             */
            volatile Node next;
            /**
             * 节点所对应的线程
             */
            volatile Thread thread;
            /**
             * nextWaiter是“区别当前CLH队列是 ‘独占锁’队列 还是 ‘共享锁’队列 的标记”
             * 若nextWaiter=SHARED,则CLH队列是“共享锁”队列;
             * 若nextWaiter=EXCLUSIVE,(即nextWaiter=null),则CLH队列是“独占锁”队列。
             */
            Node nextWaiter;
    
            final boolean isShared() {
                return nextWaiter == SHARED;
            }
            final Node predecessor() throws NullPointerException {
                Node p = prev;
                if (p == null)
                    throw new NullPointerException();
                else
                    return p;
            }
            Node() {    // Used to establish initial head or SHARED marker
            }
            Node(Thread thread, Node mode) {     // Used by addWaiter
                this.nextWaiter = mode;
                this.thread = thread;
            }
            Node(Thread thread, int waitStatus) { // Used by Condition
                this.waitStatus = waitStatus;
                this.thread = thread;
            }
        }
    
    • 入列逻辑:如队列不存在则会初始化,否则直接加入队尾。
    /**
         * 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) {
            // 根据mode判断队列模式的标志,
            Node node = new Node(Thread.currentThread(), mode);
            // Try the fast path of enq; backup to full enq on failure
            Node pred = tail;
            // 如果CLH队列非空,将节点插入队尾
            if (pred != null) {
                node.prev = pred;
                // cas实现原子更新
                if (compareAndSetTail(pred, node)) {
                    pred.next = node;
                    return node;
                }
            }
            // CLH队列为空将初始化队列
            enq(node);
            return node;
        }
        private Node enq(final Node node) {
            for (;;) {
                Node t = tail;
                if (t == null) { // Must initialize 未初始化则新增dummy head 且 head == tail
                    if (compareAndSetHead(new Node()))
                        tail = head;
                } else {
                    node.prev = t;
                    if (compareAndSetTail(t, node)) {
                        t.next = node;
                        return t;
                    }
                }
            }
        }
    

    假设CLH队列为空,插入node节点过程:

    (4.3)、AbstractQueuedSynchronizer#acquireQueued(final Node node, int arg) 节点尝试获锁,如获取不到即阻塞等待。等被唤醒的时,将会返回阻塞过程中是否曾被中断过的标记。

    /**
         * Acquires in exclusive uninterruptible mode for thread already in
         * queue. Used by condition wait methods as well as acquire.
         *
         * @param node the node
         * @param arg the acquire argument
         * @return {@code true} if interrupted while waiting
         */
        final boolean acquireQueued(final Node node, int arg) {
            boolean failed = true;
            try {
                boolean interrupted = false;
                for (;;) {
                    // 当前节点已入列, 根据公平锁的原则判断当前节点前继p是否与head相等。
                    // 队列为带头双向链表:公平原则核心体现在”p == head“即代表node为第一个节点,具有获锁的权利,否则即使被唤醒也无权利获锁。
                    final Node p = node.predecessor();
                    if (p == head && tryAcquire(arg)) {
                        setHead(node);
                        p.next = null; // help GC
                        failed = false;
                        return interrupted;
                    }
                    // 获锁失败,判断当前节点是否需要被阻塞等待
                    // 1、阻塞,即将会等待其他线程调用LockSupport#unpark 或者 收到线程中断 唤醒
                    // 2、非阻塞,即再进入for死循环竞争锁
                    if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                        interrupted = true;
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }
        
        private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
            // "前继节点"状态为Node.SIGNAL返回true 即表示当前节点将会被阻塞等待,等待"前继节点"释放锁,调用LockSupport#unpark唤醒
            // 其余返回false
            int ws = pred.waitStatus;
            if (ws == Node.SIGNAL)
                return true;
            // 状态大于0即表示取消状态
            if (ws > 0) {
                /*
                 * 从pred开始往前遍历,清除掉ws>0 即已经取消状态的节点
                 */
                do {
                    node.prev = pred = pred.prev;
                } while (pred.waitStatus > 0);
                pred.next = node;
            } else {
                /*
                 * 如果前继节点为“0”或者“共享锁”状态,则设置前继节点为SIGNAL状态。
                 * 此时需要调用者再次进入尝试,确认节点是否需要阻塞
                 */
                compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
            }
            return false;
        }
    
        private final boolean parkAndCheckInterrupt() {
            // 利用LockSupport辅助类将线程阻塞
            LockSupport.park(this);
            // 判断状态:是否被因产生中断被唤醒,还是被LockSupport#unpark唤醒
            return Thread.interrupted();
        }
    

    补充:被LockSupport#park阻塞的线程在几种情况下会被唤醒返回,因何种情况唤醒不得而知需要自我识别:

    • 调用LockSupport#unpark唤醒
    • 调用Thread#interrupt唤醒
    • 调用者无理由返回唤醒

    (4.4)、先看看AbstractQueuedSynchronizer#selfInterrupt()的代码实现

    static void selfInterrupt() {
        // 当前线程自我产生中断标记
        Thread.currentThread().interrupt();
    }
    

    为什么会在acquireQueued返回的时候还要自我产生中断呢?

    • 首先,由于parkAndCheckInterrupt方法为了识别线程由于何种原因导致线程唤醒(一般为前两种情况的判断),会调用Thread.interrupted()方法,清除中断标记并返回。
    • 其次,Thread.interrupted()清除了中断标记,将会导致后续线程操作无法识别到中断标记,所以在确定了被线程中断唤醒情况下,自己重新生成一个中断。

    (5)、AQS acquire非公平锁分析

    • 非公平锁NonFairSync和公平锁FairSync不同之处:如果锁未被占用则立即获取锁,不管节点是否为CLH队列头部。
       /**
         * Sync object for non-fair locks
         */
        static final class NonfairSync extends Sync {
            private static final long serialVersionUID = 7316153563782823691L;
            /**
             * 1、如锁未被占用,立即获取锁
             * 2、获锁失败再次进入 AbstractQueuedSynchronizer#acquire方法
             */
            final void lock() {
                if (compareAndSetState(0, 1))
                    setExclusiveOwnerThread(Thread.currentThread());
                else
                    acquire(1);
            }
            protected final boolean tryAcquire(int acquires) {
                // 实现在Sync内部
                return nonfairTryAcquire(acquires);
            }
        }
    

    由于在分析公平锁FairSync已经分析过acquire方法,这里不再赘述。

    • Sync#nonfairTryAcquire非公平锁实现:
    abstract static class Sync extends AbstractQueuedSynchronizer {
            private static final long serialVersionUID = -5179523762034025860L;
    
            abstract void lock();
            /**
             * Performs non-fair tryLock.  tryAcquire is implemented in
             * subclasses, but both need nonfair try for trylock method.
             */
            final boolean nonfairTryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                // 非公平锁:如锁未被占用,立即获取锁 
                int c = getState();
                if (c == 0) {
                    if (compareAndSetState(0, acquires)) {
                        setExclusiveOwnerThread(current);
                        return true;
                    }
                }
                // 锁可重入
                else if (current == getExclusiveOwnerThread()) {
                    int nextc = c + acquires;
                    if (nextc < 0) // overflow
                        throw new Error("Maximum lock count exceeded");
                    setState(nextc);
                    return true;
                }
                return false;
            }
            // ********
    }
    

    release释放锁

    (1)、以ReentrantLock#unlock方法释放锁为入口。注意:独占锁释放线程必须为锁持有者,否则将抛出IllegalMonitorStateException异常

    public void unlock() {
         sync.release(1);
    }
    

    (2)、sync为内部变量,实际将调用AbstractQueuedSynchronizer#release释放锁

    public final boolean release(int arg) {
            // 尝试释放锁
            if (tryRelease(arg)) {
                // 获取锁的节点必定为CLH头节点
                Node h = head;
                if (h != null && h.waitStatus != 0)
                    // 唤醒其后继节点
                    unparkSuccessor(h);
                return true;
            }
            return false;
    }
    

    将分为两个步骤:

    • tryRelease: 尝试释放锁
    • unparkSuccessor:唤醒CLH队列最靠近头节点的有效后继节点

    (2.1)、Sync类方法tryRelease逻辑很明确,即 设置state状态、清除锁持有者线程为null

    protected final boolean tryRelease(int releases) {
                int c = getState() - releases;
                if (Thread.currentThread() != getExclusiveOwnerThread())
                    throw new IllegalMonitorStateException();
                boolean free = false;
                // 由于可重入功能,需要完全释放为0才实际释放锁
                if (c == 0) {
                    free = true;
                    setExclusiveOwnerThread(null);
                }
                setState(c);
                return free;      
    }
    

    (2.2)、AbstractQueuedSynchronizer#unparkSuccessor方法唤醒继节点,即最靠近头节点的有效后继节点

    private void unparkSuccessor(Node node) {
            /*
             * 如果当前节点状态为负数(意味着可能会被再次唤醒),因而设置该状态为0
             * 节点状态修改可能失败或者被其他等待线程修改,但不影响逻辑。
             */
            int ws = node.waitStatus;
            if (ws < 0)
                compareAndSetWaitStatus(node, ws, 0);
             
            /*
             * 节点释放锁则唤醒队列的下一个节点, 即:
             * 1、下一个节点不为null 且 状态为非取消 即唤醒。
             * 2、上述对Node节点分析, 下一个节点waitStatus>0表示为取消状态,此时从末尾遍历至node节点,
             *    找到最后一个非取消状态节点并唤醒,否则不做任何操作。
             */
            Node s = node.next; 
            if (s == null || s.waitStatus > 0) {
                s = null;
                for (Node t = tail; t != null && t != node; t = t.prev)
                    if (t.waitStatus <= 0)
                        s = t;
            }
            if (s != null)
                LockSupport.unpark(s.thread);
    }
    

    公平锁 和 非公平锁释放锁逻辑一致,只需分析一个即可。

  • 相关阅读:
    图论--曼哈顿距离最小生成树模板
    图论--生成树计数模板
    图论--欧拉回路--弗罗莱算法模板
    Codeforce 1255 Round #601 (Div. 2)D. Feeding Chicken (模拟)
    pta 习题集5-17 家谱处理
    pta习题集5-16 地下迷宫探索
    pta 习题集5-17 哥尼斯堡的“七桥问题”
    pta习题集5-16 朋友圈
    pta 习题集5-19 列车厢调度
    pta 习题集5-18 打印学生选课清单
  • 原文地址:https://www.cnblogs.com/xiaoxing/p/12982648.html
Copyright © 2011-2022 走看看