AQS(1)—— 互斥模式获取

152 阅读4分钟

互斥模式获取

  在互斥(独占)模式下获取,忽略中断。 通过至少调用一次tryAcquire ,并在成功后返回。 否则,将线程排队,可能反复阻塞和解除阻塞,直到调用tryAcquire成功为止。 此方法可用于实现Lock.lock方法。

  成功修改state的值,意味着获取成功。

获取逻辑

// 在独占模式下获取,并忽略中断
public final void acquire(int arg) {
    // tryAcquire(arg):由具体的子类实现
    // 当获取成功时,则跳出该方法,避免阻塞
    if (!tryAcquire(arg) &&
        // 当获取失败时,将当前线程封装成Node节点,加入到等待队列
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        // 如果 acquireQueued() 方法返回true,则中断当前线程
        // 内部是一行很简单的代码,Thread.currentThread().interrupt();
        // 将当前线程置为中断状态,响应不响应该中断 由开发者决定
        selfInterrupt();
}

  响应还是不响应中断,由开发者决定

public class LockTest {
    public staic void main(String[] args) {
        Lock lock = new ReentrantLock();
        lock.lock();
        try {
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("线程被中断了,跳出业务逻辑");
            } else {
                System.out.println("执行业务逻辑");
            }
        } finally {
            lock.unlock();
        }  
    }
}

入队逻辑

// 创建节点,并加入到队列尾部
private Node addWaiter(Node mode) {
    // mode:模式,表示当前节点是由于什么模式而加入到队列
    // 将当前线程和模式封装成Node节点(this.nextWaiter = mode)
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    // 尾节点不为null,代表等待队列已初始化
    if (pred != null) {
        // 将 新节点 指向(👆) 尾节点(此时就已经入队成功,剩下两个指针未调整)
        node.prev = pred;
        // (指针一)通过CAS 修改尾节点,将其指向新节点
        if (compareAndSetTail(pred, node)) {
            // (指针二)将旧的尾节点指向(👇)新节点(当前节点已经成为新的尾节点)
            pred.next = node;
            return node;
        }
    }
    // CAS修改失败或者等待队列未初始化,进入该逻辑
    enq(node);
    return node;
}
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        // 尾节点为null,代表等待队列还未初始化
        if (t == null) { // Must initialize,初始化逻辑
            // CAS原子修改,将头节点指向一个空的Node节点(Thread为null)
            if (compareAndSetHead(new Node()))
                // 此时尾节点和头节点指向同一个空的Node节点
                tail = head;
        } else {
            // 将新节点指向(👆)尾节点
            node.prev = t;
            // 通过CAS修改尾节点,将其指向新节点
            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);
                // 将前置节点的引用清除,帮助GC
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // shouldParkAfterFailedAcquire() 方法返回false时,
            // 再一次进入for循环,表示当前节点还有一次机会尝试获取
            // shouldParkAfterFailedAcquire() 方法返回true时,
            // 进入 parkAndCheckInterrupt() 方法,阻塞当前线程
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                // 当线程在等待时间内被中断,在被唤醒时
                // 会修改中断状态为 true,但还是要进入for循环直到获取锁成功
                interrupted = true;
        }
    } finally {
        // 该方法下,应该是没有情况进入if代码块
        if (failed)
            cancelAcquire(node);
    }
}
// 获取失败时,应该阻塞吗
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 获取前置节点的等待状态
    int ws = pred.waitStatus;
    // 如果状态为 -1 ,则直接返回true
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        // 前驱节点已经被置为-1状态,当前节点可以安心阻塞
        return true;
    // 如果状态大于 0,代表前置节点被取消
    if (ws > 0) {
        状态大于0,代表被取消,需要跳过这些节点
        do {
            // 向上一直寻找节点,直到不为取消状态,并将当前节点(👆)指向它
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        // 未取消的节点指向(👇)当前节点,形成双向链表
        pred.next = node;
    } else {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don't park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
        // 状态必须为0或者-3,
        // 将前置节点的等待状态置为 -1 
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
private final boolean parkAndCheckInterrupt() {
    // 阻塞当前线程
    LockSupport.park(this);
    // 当被唤醒时,返回其中断状态(注意:interrupted()方法会清空中断状态)
    return Thread.interrupted();
}