JVM本地锁(二)ReentrantLock可重入锁源码解析
创始人
2024-05-03 18:39:49
0

什么是可重入锁呢
顾名思义,就是可以重复进入的锁,学过操作系统或者计组的可参照理解pv,或者多重中断。

demo1(){lock(); //第一次锁demo2(){lock(); // 第二次锁unlock(); }unlock();}

文章目录

  • ReentrantLock
    • lock 加锁
      • 1. ReentrantLock.lock()
      • 2. sync.lock()
      • 3. unfairSync.lock()
      • 4. AQS.acquire(1)
    • unlock 解锁
      • 1. ReentrantLock.unlock()
      • 2. AQS.release(1)
      • 3. syn.tryRelease(1)
  • 总结

ReentrantLock

lock 加锁

在这里插入图片描述

1. ReentrantLock.lock()

直接从lock()入手翻阅源码

public void lock() {sync.lock();}

它调用的是sync.lock();

2. sync.lock()

在这里插入图片描述
在ReentrantLock初始化时,默认是非公平锁,有参构造true则是公平锁

非公平锁:来个线程就先试试能不能插队,不能插队才去后面排队
公平锁:线程都乖乖去后面排队去,不准插队

总而言之,sync就是个内部非公平/公平锁。

再往下看,由于lock()是抽象方法,而sync默认是非公平锁。
在这里插入图片描述

调用unfairSync.lock()

3. unfairSync.lock()

final void lock() {// 若CAS抢到锁,记录设置当前线程if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());else// 若没抢到锁acquire(1);
}

点击compareAndSetState

在这里插入图片描述
这就是CAS获取锁,unsafe是JDK中用于硬件实现CAS的操作。
不用管它,只需要理解这里就是CAS操作。

若state==0,则更新为1,并且设置好排他线程。即该线程成功抢到锁

那如果没抢到锁呢

4. AQS.acquire(1)

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

先执行tryAcquire(),可以理解为再次抢锁。

  • 如果成功,返回为true,再加个!,成了false,后面就不用执行
  • 如果失败,则执行后面acquireQueued(),即进入等待队列

这里nonfairSync重写方法,直接调用nonfairSync.tryAcquire(1)

在这里插入图片描述
继续往下调用nonfairTryAcquire(1)

final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();// 如果state=0,再重新尝试一下看能不能抢到锁if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}// 如果state不为0// 如果这个线程就是之前已经抢到锁的那个,它又要加锁,重入!else if (current == getExclusiveOwnerThread()) {// state递加上去int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}

简单理解就是

  • 如果state==0,再重新CAS抢救一下,看能不能抢到锁,抢到了那就成功
  • 如果state不为0,说明已经被抢了。但是如果那个抢到锁的线程是自己,自己又重入了,那state+1,再次加锁,成功。
  • 如果那个抢到锁的不是自己,加锁失败。

如果加锁失败,则方法返回到AQS.acquire(1),还需要执行if后面的判断。
简单看下addWaiter();

private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failure// tail即尾结点Node pred = tail;if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}enq(node);return node;}

这里维护着一个双向链表,简单来说就是把结点放到链表的尾部,并且更新尾结点。
加锁失败了,把这个线程放在表尾,乖乖排队吧。

这就是加锁的所有过程。

unlock 解锁

1. ReentrantLock.unlock()

在这里插入图片描述
很明显,看来这里是将之前的state一个个减1减回来

2. AQS.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也就是解锁了

3. syn.tryRelease(1)

在这里插入图片描述
就是将state-1,进行解锁操作。由于可能被可重入了,state-1后不一定为0;如果为0则将记录的线程清空。

解锁很好理解,就不详细赘述了。

总结

lock:

  • CAS获取锁,若没有线程占用锁(state==0),加锁成功并记录当前线程是有锁线程(两次,开始一次,acquire()中又一次)
  • 若state值不为0,说明锁已经被占用,则判断当前线程是否是有锁线程,若是则重入(state+1)
  • 否则加锁失败,入队等待

unlock:

  • 判断当前线程是否是有锁线程,不是则抛出异常
  • state-1, 若-1后state值为0则解锁成功,返回true
  • 若-1后state不为0,则返回false

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...