【Java 并发编程】ReentrantLock
ReentrantLock
ReentrantLock 是一个可重入的互斥锁,又被称为“独占锁”。
ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。
ReentrantLock 锁在同一个时间点只能被一个线程锁持有;而可重入的意思是,ReentrantLock 锁,可以被单个线程多次获取。
ReentrantLock 特性概览
ReentrantLock 意思为可重入锁,指的是一个线程能够对一个临界资源重复加锁。这里,我们先将 ReentrantLock 跟常用的 Synchronized 进行比较:
特性 | ReentrantLock | Synchronized |
---|---|---|
锁实现机制 | 基于AQS | 监视器模式 |
灵活性 | 支持响应中断、超时、尝试获取锁 | 不灵活 |
释放形式 | 必须显式调用 unlock() 释放锁 | 自动释放监视器 |
锁类型 | 公平锁、非公平锁 | 非公平锁 |
条件队列 | 可关联多个条件队列 | 关联一个条件队列 |
是否可重入 | 是 | 是 |
【示例】
public class LockTest { public void test () throws Exception { // 1.初始化选择公平锁、非公平锁 ReentrantLock lock = new ReentrantLock(true); // 2.可用于代码块 lock.lock(); try { try { // 3.支持多种加锁方式,比较灵活; 具有可重入特性 if(lock.tryLock(100, TimeUnit.MILLISECONDS)){ } } finally { // 4.手动释放锁 lock.unlock(); } } finally { lock.unlock(); } } }
源码分析
ReentrantLock 总共有三个内部类Sync、NonfairSync、FairSync。其源码如下:
public class ReentrantLock implements Lock, java.io.Serializable { private final Sync sync; abstract static class Sync extends AbstractQueuedSynchronizer { ... } static final class FairSync extends Sync { ... } static final class NonfairSync extends Sync { ... } ... }
NonfairSync 与 FairSync 类继承自 Sync 类,Sync 类继承自 AbstractQueuedSynchronizer 抽象类:
抽象同步器:Sync
Sync 类的方法和作用如下:
方法 | 作用 |
---|---|
void lock() | 锁定,抽象方法 |
boolean nonfairTryAcquire(int acquires) | 非公平地方式锁定 |
boolean tryRelease(int releases) | 尝试在共享模式下获取对象状态 |
boolean isHeldExclusively() | 判断资源是否被当前线程占有 |
ConditionObject newCondition() | 新生成一个条件 |
Thread getOwner() | 返回占有资源的线程 |
int getHoldCount() | 返回状态 |
boolean isLocked() | 资源是否被占用 |
void readObject(java.io.ObjectInputStream s) | 自定义反序列化逻辑 |
部分源码如下:
public class ReentrantLock implements Lock, java.io.Serializable { abstract static class Sync extends AbstractQueuedSynchronizer { ... abstract void lock(); // 非公平方式获取 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { // 抢占式,CAS 方式设置状态成功,状态 0 表示锁没有被占用 setExclusiveOwnerThread(current); // 设置当前线程独占 return true; } } else if (current == getExclusiveOwnerThread()) { // 当前线程拥有该锁 int nextc = c + acquires; // 增加重入次数 if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } // 试图在共享模式下获取对象状态,此方法应该查询是否允许它在共享模式下获取对象状态,如果允许,则获取它 protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) // 当前线程不为独占线程 throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; } } ... }
具体的流程:
非公平同步器:NonfairSync
NonfairSync 类继承了 Sync 类,表示采用非公平策略获取锁,实现了 Sync 类中抽象的 lock 方法,源码如下:
public class ReentrantLock implements Lock, java.io.Serializable { static final class NonfairSync extends Sync { final void lock() { if (compareAndSetState(0, 1)) // 抢占式,比较并设置状态成功,状态0表示锁没有被占用 setExclusiveOwnerThread(Thread.currentThread()); // 把当前线程设置独占了锁 else // 锁已经被占用,或者set失败 acquire(1); // 以独占模式获取对象,忽略中断 } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } } ... }
每一次 lock 调用,都会尝试获取锁,然而它并不会按照公平等待的原则进行等待,而是抢占式地获取锁。
如果线程抢占失败,就会调用 acquire(1)
,进而通过 tryAcquire()
获取锁。在这种情况下,如果获取锁失败,就会调用 addWaiter()
加入到等待队列中去。
公平同步器:FairSync
FairSync 类也继承了 Sync 类,表示采用公平策略获取锁,其实现了 Sync 类中的抽象 lock 方法,源码如下:
public class ReentrantLock implements Lock, java.io.Serializable { static final class FairSync extends Sync { final void lock() { acquire(1); // 以独占模式获取对象,忽略中断 } 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()) { // 状态不为0,即资源已经被线程占据 int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } } ... }
公平锁 FairSync 的 lock 方法,使用了 AQS 的默认实现,当资源空闲时,它总是会先判断 sync 队列是否有等待时间更长的线程,如果存在,则将该线程加入到等待队列的尾部,实现了公平获取原则。
小结
在 ReentrantLock 里面,不管是公平锁(FairSync#tryAcquire),还是非公平锁(Sync#nonfairTryAcquire),都有一段通过 state 控制重入的类似逻辑。其中,state 这个字段主要的作用:
-
state 初始化的时候为 0,表示没有任何线程持有锁。
-
当有线程持有该锁时,值就会在原来的基础上 +1,同一个线程多次获得锁是,就会多次 +1,这里就是可重入的概念。
-
解锁也是对这个字段 -1,一直到 0,此线程对锁释放。
参考:
本文作者:LARRY1024
本文链接:https://www.cnblogs.com/larry1024/p/17769320.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步