ReentrantLock解锁流程

ReentrantLock解锁流程

上篇介绍了ReentrantLock的加速流程,传送门:https://blog.csdn.net/qq_25178353/article/details/107568396 有加锁就有解锁,接下来探讨一下解锁的流程。解锁流程总体来说比这加锁简单。先上流程图:

ReentrantLock解锁流程_第1张图片

解锁流程从调用lock.unlock()开始,lock.unlock()方法调用的是sync.release(1)方法,sync.release(1)的代码如下:

    public final boolean release(int arg) {

        if (tryRelease(arg)) {
            //当 tryRelease返回true 即 c == 0 时 ,
            //需要唤醒头结点的后继节点
            Node h = head;
            // 当waitStatus == -1 时说明后继节点正在park中需要unpark
            // 当waitStatus == 0时,后继节点是不会park的会在竞争一轮锁
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

release(int arg) 方法是sync的父类AbstractQueuedSynchronizer实现的,其调用了tryRelease(arg),tryRelease(arg)跟tryAquire一样,在AQS里面 没有具体实现,需要子类实现,下面贴出ReentrantLock的tryRelease(arg) 的代码:

        protected final boolean tryRelease(int releases) {
            // 获取本次释放锁后state的值 执行unlock时一般为 release的值为1
            int c = getState() - releases;
            // 若当前线程不是拥有锁的线程,则抛出异常,只有拥有锁的线程才可以释放锁
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            // 若本次解锁后 c 的值为0 说明该线程已经完全释放了锁
            // 若c不为0,则是在多次重入的情况下。
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            //当c == 0时,这一步执行完,该线程就真正释放了锁,其他线程就可以拥有锁了,
            //因为还没有执行unpark方法,还没有唤醒该线程的后继节点的线程,
            //所以在非公平锁的情况下,未入队的线程比这等待队列里的线程更容易竞争到锁
            setState(c);
            return free;
        }

tryRelease后,(若锁的状态为0(即state == 0)&& 等待队列不为空 && head.waitStatus != 0) 就可以唤醒head的后继节点。关于waitStatues !=0 这个条件,这里做一下简要说明:waitStatus ==0 时,改节点的后继节点还没有park所以不需要unpark。下面看一下unparkSuccessor(h)的代码:

    private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            // 尝试将ws改为 0 ,即使失败也无所谓。
            // 这里使用cas的方式修改是因为他的前置节点也可能修改他的waitStatus
            // 将ws更新为0的理由是让唤醒的线程可以多一轮竞争。提高竞争率
            compareAndSetWaitStatus(node, ws, 0);
        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);
    }

unparkSuccessor是AQS的一个私有方法,只能该类内部调用。当调用完LockSupport.unpark(s.thread)方法后,唤醒在park状态的线程s.thread。唤醒不等于拥有锁,因为s.thread被唤醒后需要通过tryAcquire() 方法去竞争,如果竞争失败则在竞争一次如果两次都没有竞争则继续阻塞,等着获取的线程再次唤醒自己。

 

 

 

 

 

 

 

你可能感兴趣的:(ReentrantLock,java)