AQS解锁原理

ReectrantLock 和 AQS 是什么关系

ReectrantLock 是在 AQS 外面包了一层,ReectrantLock 所有的加锁、解锁、打断、取消等等,底层都是交给 AQS完成的。

ReectrantLock 标准使用方式

 class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     // 加锁
     lock.lock();  // block until condition holds
     try {
        // 被加锁的代码块
       // ... method body
     } finally {
       // 解锁
       lock.unlock()
     }
   }
 

解锁过程

public void unlock() {
    // 调用非公平锁的释放锁方法。 
    sync.release(1);
}
public final boolean release(int arg) {
    // 释放锁,
    if (tryRelease(arg)) {
        // 拿头结点。
        Node h = head;
        // t头结点不是空,而且头结点的 waitStatus 此时是 -1。
        if (h != null && h.waitStatus != 0)
            // 唤醒离头结点最近的没有被取消的节点。
            // 这里是传了头结点进去。
            unparkSuccessor(h);
        return true;
    }
    return false;
}

释放锁的细节:

protected final boolean tryRelease(int releases) {
    // 这跟锁重入有关系,当 c == 0 时,才能释放成功。
    // 比如,T0 加锁四次,getState() == 4,释放三次 releases ==3
    // c=4-3, T0 当然不能释放成功,还有一层锁呢。
    int c = getState() - releases;
    // 如果当前线程不是锁的持有者,抛异常。
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 当 c==0 时,锁才能释放成功。
    if (c == 0) {
        free = true;
        // 将同步器中锁的持有者置为 null。
        setExclusiveOwnerThread(null);
    }
    // CAS 修改 state = 0。
    setState(c);
    // 返回
    return free;
}

唤醒离头结点最近的没有被取消的节点,在头结点后面 可能有节点(线程)已经被取消了,那就将这些被取消的线程从队列中移除,这个样的话,第一个没有被取消的节点就放在了头结点后面,也就是队列中的第二个节点的位置。

// node == head
private void unparkSuccessor(Node node) {
    // ws == -1
    int ws = node.waitStatus;
    if (ws < 0)
        // 设置队头节点的 waitStatus == 0
        compareAndSetWaitStatus(node, ws, 0);    
    // s 先指向队列中的第二个节点。
    Node s = node.next;
    // 如果第二个节点是空,或者被取消了。
    if (s == null || s.waitStatus > 0) {
        s = null;
        // t 从队尾开始,朝着队头的方向,扫一遍队列中的所有节点。
        for (Node t = tail; t != null && t != node; t = t.prev)
            // s 指向离队头最近的且没有被取消的线程节点。
            if (t.waitStatus <= 0)
                s = t;
    }
    // unpark s 指向的节点中的线程。
    if (s != null)
        LockSupport.unpark(s.thread);
}

唤醒 node 后,node还要继续去竞争锁。

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);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 竞争失败,继续追加到队列里 park。
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt()) // 从这里唤醒的。
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

被唤醒变了怎么还要去竞争锁,而且还有失败的可能性?

因为这是非公平锁,既有被唤醒的,可能同时还有刚产生的线程,两者会竞争锁。

总结

释放锁过程做三件事情:

  1. 释放锁
  2. 唤醒离头结点最近的第一个没有被取消的 node。
  3. 被唤醒的 node 再去竞争锁,成功,node出队执行逻辑代码,失败,继续追加到队列里 park。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值