【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,此线程对锁释放。
参考: