ReentrantLock锁底层源码分析

主要针对lock()、unlock()、锁的可重入性进行源码分析

先来看一个测试代码

public class Test7878 {

    public static ReentrantLock lock = new ReentrantLock(true);
    
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"线程抢到锁,执行业务逻辑");
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        },"t1");
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName()+"主线程启动线程1");
            t1.start();
            System.out.println(Thread.currentThread().getName()+"主线程先抢锁");
            Thread.sleep(5000L);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
        System.out.println(Thread.currentThread().getName()+"主线程执行完毕");
    }
}

创建了一个异步线程t1。与主线程互相抢夺lock锁
先来看一下公平锁的实现

Lock()

final void lock() {
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

接着看到tryAcquire底层实现

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

断点跟过来
在这里插入图片描述
此时初始状态下c==0,因此进入第一个条件判断,首先是hasQueuedPredecessors()
debug跟进来

 public final boolean hasQueuedPredecessors() {
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

hasQueuedPredecessors()作用:

返回true:当头节点不等于尾节点并且(1.头节点指向的下一个节点为空<或者>2.头节点的下一个元素节点不是为当前线程)
解释一下:这里的链表结构为双向链表结构,存放的是除了当前加锁的线程外,其余的等待线程。比较特殊的是当前持有锁的线程并不在链表中,而是单独用一个变量来记录,因此头节点一般是null的。
可以简单理解为,此时线程要不要排队呢?
所以第一次进来,h=null,t=null,h!=t直接不满足,后面都不需要判断,因此不需要排队,直接返回false。

所以在并发的情况下,多个线程,只有一个是不需要排队的,然后才会进入后面的cas。
这也就是与非公平锁的区别,非公平锁不管排队不排队,直接开始cas,谁能cas成功,谁执行代码。

此时代码来到CAS
在这里插入图片描述
当前就主线程,因此第一次肯定CAS成功。
Cas成功,那么就设置exclusiveOwnerThread为当前线程
在这里插入图片描述
然后返回true,执行主线程代码
此时代码断点打在return false这里,准备看t1异步线程执行失败的情况。
一按f9,代码随机跳到return false这里
试运行一下

在这里插入图片描述

看到,确实当前线程t1,已经抢锁失败,返回false了。
然后!tryAcquire(arg)也就返回true了,执行后面的代码
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
底层源码如下

    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;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

进入自旋过程
此时双向链表结构如下
在这里插入图片描述
可以看到当前传入的Node为
在这里插入图片描述
所以说,node获取前一个节点也就是,null的头节点,因此进入这个代码

p == head && tryAcquire(arg)
p==head

该条件满足,在走一次公平锁cas的过程,此时c0,依然走c0这个里面逻辑,此时看到
在这里插入图片描述

h!=t,条件满足了,说明可能要排队,再看看后面。
(s = h.next) == null:看看头节点的next是不是还有指向节点?是否非空,看debug控制台就知道,肯定不是,所以第一个判断是false,第二个呢,s.thread也就是t1,t1就是当前线程,所以肯定也不满足不等于条件。所以这里最终返回false也就是不需要需要排队。

然后就进入了cas
在这里插入图片描述
此时cas肯定是成功的,因为主线程已经执行完成,释放锁了。
最后debug跟过来,返回true。
在这里插入图片描述
最后控制台输出

main主线程启动线程1
main主线程先抢锁
main主线程执行完毕
t1线程抢到锁,执行业务逻辑

所以,异步线程cas失败后执行流程就分析完了。

如果此时主线程一直不释放锁呢?
断点直接再次来到tryAcquire()的return false这里。
看到acquireQueued()方法
在这里插入图片描述
刚才tryAcquire()为true,cas成功,但这里主线程没有释放锁,所以cas就失败了这里。
所以代码走到下面去
在这里插入图片描述
先看到shouldParkAfterFailedAcquire()方法
由于此时ws=0,所以逻辑跟到else里面
在这里插入图片描述
这里也就是cas,把ws从0变成这个枚举值-1,那这里是为啥呢,主要是防止自旋的时候空转。怎么实现的,那么接下来,自旋再次执行,代码依然走到这个方法,此时
在这里插入图片描述
ws = -1作为标识,说明我刚刚已经执行过了,所以直接返回true,返回true之后。
代码往上一步方法看
在这里插入图片描述

刚刚返回shouldParkAfterFailedAcquire()方法false,这次返回true,所以此时走到parkAndCheckInterrupt()方法
这里面就是解决由于cpu空转而产生CPU飙高问题的的核心。
代码跟进来
在这里插入图片描述
此时直接用到了LockSupport.park(this)去。直接挂起当前线程,所以代码在这里就执行完了,如果主线程不通过LockSupport.unpark()方法唤醒,那么代码就暂时一直卡在这了。
在这里插入图片描述
此时,F9一按,跳掉断点,发现断点直接不会走下去。所以当前t1线程也就被挂起了,所以就不会再去自旋了。

那么解锁的原理是什么呢?
点到源码unlock

在这里插入图片描述
在这里插入图片描述
点到这个方法里面,当前线程为main主线程。
此时h非空满足,但是状态还是0,所以不满足,因此此时直接释放锁,t1抢到锁。t1也是同样逻辑。

那另一个唤醒逻辑咋执行的呢,我们先让t1cas多失败几次然后挂起。
直接断点打在
在这里插入图片描述
此时t1直接挂起
然后到释放锁
断点提前打在return Thread.interrupted()这里
此时代码就走到了
在这里插入图片描述
即当前线程就为主线程
在这里插入图片描述
先判断
在这里插入图片描述
W<0,因为刚刚已经cas改成-1了,目的就是防止cpu空转,所以这时候又要cas改成0,为了一会可以成功的unlock()
接着代码取到
在这里插入图片描述
s不为空,所以直接走到
在这里插入图片描述
到这里,也就是唤醒我们刚刚阻塞的t1线程,让他重新CAS。
此时直接F9,挑掉。
代码来到
在这里插入图片描述
就是刚刚说的提前打的断点,也就是在这被唤醒了。
此时重新cas,就成功了
在这里插入图片描述
此时返回false,lock执行结束,exclusiveOwnerThread也就是t1线程了。

所以此时,t1线程就获取到锁了。

ReentrantLock底层锁的可重入是如何实现的呢?
其实就是在加锁的时候tryAcquire()方法实现的

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

前面分析到的是c==0
下面这个判断就是对锁的可重入性的实现
只要判断到当前线程如果等于当前持有锁的线程,然后把c+入参传入的acquires,也就是0+1=1,然后再把1设置到state里面去,保存锁的重入次数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

原味的你

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值