JAVA并发(7)—AQS源码解析(独占锁-解锁过程)

开发中需要注意,一定要在finally块中释放锁。

公平锁的解锁过程

public void unlock() {
    sync.release(1);
}
public final boolean release(int arg) {  
    if (tryRelease(arg)) {  
        Node h = head;  
        if (h != null && h.waitStatus != 0)  
            unparkSuccessor(h);  
        return true;  
    }  
    return false;  
}  
  • tryRelease(arg):该方法有AQS子类实现,为具体释放锁的过程;
  • unparkSuccessor:唤醒后继线程;

1. 试图释放—tryRelease

相比获取锁的过程,此处并没有CAS操作。因为只有一个线程会获取到锁,那么该处代码就是线程安全的操作。

protected final boolean tryRelease(int releases) {  
    //state记录了锁重入的次数。而现在调用释放锁的流程,那么直接减一。
    int c = getState() - releases; 
    //释放锁的线程必须是当前持有锁的线程
    if (Thread.currentThread() != getExclusiveOwnerThread())  
        throw new IllegalMonitorStateException();  
    boolean free = false;  
  //若是c==0,证明完全释放锁。
    if (c == 0) {  
        free = true;  
        setExclusiveOwnerThread(null);  
    }  
   //修改state的值。
    setState(c);  
    return free;  
}  
    protected final void setState(int newState) {
        state = newState;
    }

2. 唤醒线程—unparkSuccessor

若完全释放锁,会调用该方法。

private void unparkSuccessor(Node node) {  
    //获取头节点的waitStatus,若下于0,则设置为0
    int ws = node.waitStatus;  
    if (ws < 0)  
        compareAndSetWaitStatus(node, ws, 0);  
   //通常情况下,要唤醒的就是自己的后继节点
    Node s = node.next;  
    //若后继节点不存在或者后继节点已经被取消(waitStatus>0)
    if (s == null || s.waitStatus > 0) {  
       //将后继节点指向null,
        s = null;  
        //尾指针向前遍历
        for (Node t = tail; t != null && t != node; t = t.prev)  
           //找到之后,依旧往前找,直到找到距离head最近的s节点
            if (t.waitStatus <= 0)  
                s = t;  
    }  
    //若是最终找到,那么唤醒这个线程。
    if (s != null)  
        LockSupport.unpark(s.thread);  
}  
JAVA并发(7)—AQS源码解析(独占锁-解锁过程)_第1张图片
image.png

tryRelease方法修改了status。而unparkSuccessor方法便准备唤醒线程。

在上文中,分析shouldParkAfterFailedAcquire方法的时候,我们提到了当一个线程准备挂起时,它会将前驱节点的中的waitStatus属性设置为SIGNAL(-1)。

if (h != null && h.waitStatus != 0)  
   unparkSuccessor(h);  

而唤醒线程的条件便是头节点的waitStatus不为0。这个标识为是nextNode设置的。原因是:nextNode要求当前节点释放锁时,需要唤醒后续节点。而sync queue的tail节点的waitStatus为0,是因为没有Node要求tail Node后续执行唤醒逻辑。

如何waitStatus不设置为-1有什么后果?

如果不设置,若是waitStatus=0也唤起后续线程,那么会导致最后一个节点也试图去调用唤醒的逻辑。

为什么尾遍历
由于在上文提到,构建sync queue时,CAS结束,(旧)尾结点还未next(新)尾结点时,若遍历整个链表,可能无法遍历到tail节点。于是AQS便从尾到头遍历。直到找到距离head节点最近的waitStatus<=0的节点。

推荐阅读

https://segmentfault.com/a/1190000015752512

相关阅读

JAVA并发(1)—java对象布局
JAVA并发(2)—PV机制与monitor(管程)机制
JAVA并发(3)—线程运行时发生GC,会回收ThreadLocal弱引用的key吗?
JAVA并发(4)— ThreadLocal源码角度分析是否真正能造成内存溢出!
JAVA并发(5)— 多线程顺序的打印出A,B,C(线程间的协作)
JAVA并发(6)— AQS源码解析(独占锁-加锁过程)
JAVA并发(7)—AQS源码解析(独占锁-解锁过程)
JAVA并发(8)—AQS公平锁为什么会比非公平锁效率低(源码分析)
JAVA并发(9)— 共享锁的获取与释放
JAVA并发(10)—interrupt唤醒挂起线程
JAVA并发(11)—AQS源码Condition阻塞和唤醒
JAVA并发(12)— Lock实现生产者消费者

你可能感兴趣的:(JAVA并发(7)—AQS源码解析(独占锁-解锁过程))