专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

ReentrantLock 解锁过程源码详解

上个文章加锁的问题

  • 为什么t2要初始化一个无内容的Head?
  • 为什么t3要修改t2的waitStatus=-1?
  • 可重入锁怎么解锁的?
  • Node存线程有什么用?

带着问题阅读源码

以t1unlock为例

unlock

   public void unlock() {
        sync.release(1);
    }

release

    public final boolean release(int arg) {
    //1.tryRelease 尝试释放锁
        if (tryRelease(arg)) {
            Node h = head;
        //2.释放锁成功之后,有两个判断
        //3.head = null 如果没有等待队列,head就为null,不为空说明有等待队列
        //4.h.waitStatus !=0 waitStatus !=0 那就是等于-1。
        //需要了解加锁过程,举个列子,t2会将head的 waitStatus 修改为-1,然后进入park
        //如果 h.waitStatus = -1 说明 h的下一节点修改的,说明h的下一节点在park,需要unpark
            if (h != null && h.waitStatus != 0)
        //5.满足条件之后,走 unparkSuccessor去upark下一个节点    
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

总结 release 需要细分为三个步骤 1.tryRelease去尝试释放锁 2.释放锁成功之后,h != null && h.waitStatus != 0 判断是否需要去唤醒等待的线程 3.unparkSuccessor去unpark线程

tryRelease

        protected final boolean tryRelease(int releases) {
        //获取state-1去解锁
            int c = getState() - releases;
        //当前线程等于持有锁的线程才能释放锁    
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
        //判断state是否等于0,什么情况不会等于0,当重入锁的时候    
        //如果不是重入锁,就直接释放锁,设置当前持有锁线程=null
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            //是重入锁,重新设置state,锁减一
            setState(c);
            return free;
        }

总结tryRelease 1.通过cas修改state来释放锁 2.如果是重入锁只是修改state 3.如果非重入锁,修改当前持有锁线程=null 4.释放锁成功返回true否则返回false

unparkSuccessor

 private void unparkSuccessor(Node node) {
//此时 node = head
//因为t2会将head也就是上一个节点的 waitStatus 修改为-1
        int ws = node.waitStatus;
//小于0就是 -1         
        if (ws < 0)
        //通过cas修改node也就是head的 WaitStatus = 0 恢复成最开始
            compareAndSetWaitStatus(node, ws, 0);
      //获取下一个节点
        Node s = node.next;
    //unLock已经判断完了 s.waitStatus = -1 才会进来
    //为什么这里还需要判断 s.waitStatus>0,因为如果线程t2被打断了,t2的waitStatus可能变成大于0
        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)
        //调用upark方法唤醒线程,此时通过node.thread获取到t2的线程
            LockSupport.unpark(s.thread);
    }

总结unparkSuccessor 1.就是在重复判断下需要唤醒的nodewaitStatus是否等于-1,防止线程被打断 2.调用unpark方法唤醒线程 3.到这里为止就算是t1释放锁,唤醒t2

唤醒线程之后从哪里开始执行

从哪里park就从哪里unpark,所以需要了解加锁的过程。上一篇文件有涉及到加锁

tech.souyunku.com5f14f3…

被唤醒的线程从 acquireQueued 开始执行

 final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
            //unpark之后执行死循环到这里
            //拿到t2上一个节点,也就是head
                final Node p = node.predecessor();
            //判断是否头节点,如果是尝试加锁,走加锁逻辑 tryAcquire
            //这里t2加锁成功 
                if (p == head && tryAcquire(arg)) {
            //设置t2为头节点,同时将 thread = null,因为没必要在持有线程了
                    setHead(node);
            //将head的next置为null        
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //加锁到这里park之后,unpark同样也是从这里开始执行
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

总结acquireQueued 1.被unpark唤醒线程之后从acquireQueued 开始执行 2.先判断被唤醒的node的上一个节点是否头节点 3.如果满足2,就调用tryAcquire去尝试获取锁 4.被唤醒的node获取锁之后设置头节点为当前node 5.获取锁之后就正常执行业务逻辑了

到这里算是把解锁说清楚了

解决问题

  • 为什么t2要初始化一个无内容的Head?在每次释放锁的时候都会去拿head来判断是否需要解锁,所以t2需要虚拟出head出来,在releas释放锁中有通过head来判断
  • 为什么t3要修改t2的waitStatus=-1?同样在releas释放锁的时候会去判断head.waitStatus是否-1,如果-1才能走解锁逻辑
  • 可重入锁怎么解锁的?可重入锁解锁是将通过casstate-1
  • Node存线程有什么用?node存线程,在调用unprak的时候可以传入需要unpark的线程

文章永久链接:https://tech.souyunku.com/26689

未经允许不得转载:搜云库技术团队 » ReentrantLock 解锁过程源码详解

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们