关于java:AQS-公平锁-非公平锁

5次阅读

共计 3340 个字符,预计需要花费 9 分钟才能阅读完成。

AQS

java.util.concurrent.locks.AbstractQueuedSynchronizer,译为形象队列式同步器
AQS 提供了原子式治理同步状态、阻塞和唤醒线程性能以及期待队列模型的简略框架;

AQS 蕴含了一个虚构的 Node 双向链表(即期待队列),由 volatile 润饰的头 & 尾节点,以及同步状态标记 state,节点期待状态标记 waitStatus,和以后线程信息

AQS 提供了共享锁与独占锁的反对

独占锁

同一个时刻只能被一个线程占有,如 ReentrantLock,ReentrantWriteLock 等,其中又蕴含了
偏心锁与非偏心锁

共享锁

同一时间点能够被多个线程同时占有,如 ReentrantReadLock,Semaphore 等

偏心锁 & 非偏心锁

初始化

    // 偏心锁
    ReentrantLock lock = new ReentrantLock(true);
    // 非偏心锁
    ReentrantLock lock = new ReentrantLock();
    ReentrantLock lock = new ReentrantLock(false);

加锁过程

java.util.concurrent.locks.ReentrantLock.FairSync#lock

    final void lock() {
        // 尝试将以后锁的状态置为 1 即为加锁胜利
        acquire(1);
    }

    public final void acquire(int arg) {
    /**
     * 尝试加锁 tryAcquire = true 则加锁胜利
     * 加锁失败时进入后续的期待流程 acquireQueued
     * acquireQueued 将有限循环获取锁
     * 若获取到锁间接开始执行 
     * 或前节点成为头部 本人行将执行 则临时挂起
     */ 
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();}
1. 尝试加锁(偏心锁模式)
    /**
     * 尝试获取锁 设置锁状态 锁状态的数值理论为获取锁线程的加锁的次数(可重入)* 返回 true 为加锁胜利 false 为失败
     */
    protected final boolean tryAcquire(int acquires) {
        // 以后线程
        final Thread current = Thread.currentThread();
        // 获取以后锁的状态
        int c = getState();
        // 以后锁状态为 0 则为能够加锁
        if (c == 0) {
            /**
             * 非偏心锁模式将不会判断队列是否有其余节点 会间接尝试获取锁
             * hasQueuedPredecessors = false 
             * 则期待队列中没有其余线程 
             * 即以后线程是下一个该当获取锁的线程
             */ 
            if (!hasQueuedPredecessors() && 
                /**
                 * 思考到有其余线程同时判断结束
                 * 故进行 CAS 操作 尝试批改锁状态
                 */ 
                compareAndSetState(0, acquires)) {
                // 前两步都已胜利 则将以后线程设置为持有锁的线程
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 以后锁状态不为 0 且持有锁的线程是以后线程 则间接重入
        else if (current == getExclusiveOwnerThread()) {
            // 设置以后锁状态 +1 即以后线程总共上锁的次数
            int nextc = c + acquires;
            // 若上锁次数为负 实为上锁次数越界
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
判断是否期待队列中是否有其余线程(非偏心锁没有此步骤)
public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    /** 
     * 队列头部 与 队列尾部元素 不相等 
     * 且头部后一个元素不为空 或头部后一个期待线程不是以后线程
     * 阐明队列中至多有其余的线程在期待
     */
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}
CAS 操作进行锁状态变更
protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

2. 加锁失败 进入期待队列

/**
 * 将以后线程构建为期待队列的元素 即 Node 
 * 下文将用 Node 示意以后线程构筑的期待队列元素
 * 将该 Node 期待的线程设置为以后锁的持有线程
 * CAS 操作 尝试将以后 Node 设置为队列尾部元素
 */ 
private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    // 判断以后尾部元素 是否为空
    if (pred != null) {
        // 尾部不为空 设置尾部元素为以后线程 Node 的前节点元素
        node.prev = pred;
        // 尝试将以后线程 Node 设置成尾部元素
        if (compareAndSetTail(pred, node)) {
            // 胜利后 批改旧的队尾元素的后一个节点为以后 Node
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}    

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        // 以后 Node 会在此处有限循环 直到前节点成为头部 本人获取到锁才返回
        for (;;) {
            // 获取以后 Node 的前节点
            final Node p = node.predecessor();
            // 若以后节点的前节点已成为队列头部 则再次尝试加锁
            if (p == head && tryAcquire(arg)) {
                // 胜利后 设置以后 Node 为头部元素
                setHead(node);
                p.next = null; // help GC
                // 此时已获取到锁 故无需再设置本人为中断状态 
                // 进入队列后果则为失败
                failed = false;
                return interrupted;
            }
            /**
             * shouldParkAfterFailedAcquire 判断以后线程是否可能挂起
             * 革除队列中已勾销的节点
             * 判断为 true 则能够挂起
             */ 
            if (shouldParkAfterFailedAcquire(p, node) &&
                /**
                 * parkAndCheckInterrupt 执行挂起
                 * 并返回以后线程是否有过中断请求
                 */
                parkAndCheckInterrupt())
                /**
                 * 以后线程在期待过程中无奈响应中断 直到获取到锁
                 * 如果在整个期待过程中被中断过 则 interrupted = true
                 * acquireQueued 最终返回 true 否则返回 false 
                 * 并在外层响应中断
                 * 须要其余线程调用以后线程的 interrupt()办法
                 */
                interrupted = true;
        }
    } finally {if (failed)
            cancelAcquire(node);
    }
}

参考资料:
https://blog.csdn.net/Java_zh…
https://blog.csdn.net/Leon_Ji…
https://blog.csdn.net/yy_dieg…

正文完
 0