介绍
ReentrantLock是Java中的一种可重入锁,也称为重入锁。是一种递归无阻塞的同步机制。它可以等同于 synchronized 的使用,但是 ReentrantLock 提供了比 synchronized 更强大、灵活的锁机制,可以减少死锁发生的概率。
ReentrantLock实现了Lock接口,它提供了以下功能:
1.可重入特性:即同一个线程可以多次获得同一把锁,而不会造成死锁。
2.公平锁和非公平锁:ReentrantLock支持公平锁和非公平锁两种模式,默认情况下是非公平锁。
3.可中断特性:支持可中断的获取锁的方法,即在等待获取锁的过程中,允许被打断等待。
4.多条件变量:ReentrantLock提供了多个条件变量,可以在一个锁上创建多个等待队列。
相对于synchronized关键字,ReentrantLock的主要优点是更加灵活和可扩展。同时,相对于synchronized关键字,ReentrantLock的性能稍微慢一些,因为它需要显式地获取和释放锁。
public class ReentrantLock implements Lock, java.io.Serializable
常量&变量
//序列化版本号
private static final long serialVersionUID = 7373984872572414699L;
/** Synchronizer providing all implementation mechanics */
//同步器
private final Sync sync;
构造方法
- ReentrantLock() 这是ReentrantLock的无参构造方法,它会创建一个非公平锁(fairness参数默认为false),即当多个线程争抢同一个锁时,先获取锁的线程不一定是先发起请求的线程。
- ReentrantLock(boolean fairness) 这是ReentrantLock的带参构造方法,fair参数指定了锁的公平性,如果fair为true,则表示该锁是公平锁,即多个线程争抢同一个锁时,先发起请求的线程会优先获取锁。如果fair为false,则表示该锁是非公平锁,即多个线程争抢同一个锁时,先获取锁的线程不一定是先发起请求的线程。
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
内部类
ReentrantLock的内部类Sync是通过继承AQS(AbstractQueuedSynchronizer)实现的可重入锁。它维护了锁的状态和同步队列。
Sync中最重要的是tryAcquire(int acquires)方法和tryRelease(int releases)方法,它们实现了获取锁和释放锁的逻辑。在这两个方法中,会判断当前线程是否已经持有锁,如果已经持有则直接返回true,否则会将当前线程加入同步队列中,阻塞等待获取锁。
此外,Sync还重写了AQS中的其他方法,如tryAcquireShared(int acquires)和tryReleaseShared(int releases),以支持共享锁的获取和释放。
总之,Sync作为ReentrantLock的核心部分,为ReentrantLock提供了可重入锁机制的实现,保证了多线程环境下的线程同步和数据一致性。
Sync
/**
* Base of synchronization control for this lock. Subclassed
* into fair and nonfair versions below. Uses AQS state to
* represent the number of holds on the lock.
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
abstract void lock();
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
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;
}
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
// Methods relayed from outer class
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {
return getState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
公平锁FairSync
FairSync是ReentrantLock的内部类,它继承了AQS(AbstractQueuedSynchronizer)类,并重写了tryAcquire方法和tryRelease方法。试图获取锁的线程会按照先进先出的顺序进行排队,并在锁可用时按照队列的顺序获取锁。因此,FairSync实现了公平锁的机制。
需要注意的是,由于FairSync实现了公平锁的机制,因此在高并发环境下性能可能会有所下降。因此,在实际开发中,需要根据具体情况来选择使用公平锁还是非公平锁。
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
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;
}
}
公平锁中,直接调用 acquire 方法。
非公平锁NonfairSync
NonfairSync继承自Sync类,Sync类是ReentrantLock的同步基础类。NonfairSync的实现方式是,当一个线程释放锁时,它会检查是否有等待线程,如果有,则唤醒其中的一个线程。如果有多个等待线程,则唤醒其中一个线程的选择是任意的,没有任何保证。这种实现方式被称为“非公平锁”,因为等待时间比较长的线程可能会一直处于等待状态,直到某个线程释放锁时才被唤醒。
总的来说,NonfairSync的实现方式比较简单,而且效率也比较高。如果不需要对线程的执行顺序进行特殊控制,可以使用NonfairSync来实现锁的同步机制。
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
非公平锁中,首先尝试将 AQS 中的 state 属性从 0 变成 1,如果成功,则代表获取锁资源成功;否则再调用 acquire 方法。
常用方法
ReentrantLock的实现借用了AQS,AQS中包含一个队列,当一个线程调用ReentrantLock的lock方法时,若此锁已经被别的线程获取,那么当前线程就会被封装为AQS中队列的节点,加入等待队列,调用park挂起,直到别的线程调用unlock解锁(内部是unpark),当前线程会被唤醒然后获取锁。
相关AbstractQueuedSynchronizer的方法,请参考AbstractQueuedSynchronizer源码
lock
lock内部调用了内部抽象类Sync(继承了AQS,AbstractQueuedSynchronizer)的实例sync的lock方法,此方法有两个实现:公平锁、非公平锁。对应两个实现了Sync的内部类:FairSync和NonfairSync
在公平锁的实现中,获取锁的lock方法实现为:
final void lock() {
//获取锁
acquire(1);
}
非公平锁中,lock的实现为:
final void lock() {
// 尝试获取锁,也就是尝试把AQS中的state从0改为1
if (compareAndSetState(0, 1))
// 来到这里时,说明修改state成功,即获取锁资源成功
// 将当前线程设置到AOS的exclusiveOwnerThread属性中,代表该线程拿到了锁资源(跟可重入锁有关)
setExclusiveOwnerThread(Thread.currentThread());
else
// 再去获取锁
acquire(1);
}
ReentrantLock的lock方法用于获得锁。它具有以下特点:
- 如果锁当前没有被其他线程持有,该方法会立即获得锁,并返回。
- 如果锁当前被其他线程持有,则当前线程会被阻塞,直到该锁被释放。
- 当前线程获得锁后,它可以重复调用lock方法多次,每调用一次就会增加锁的计数器。只有在锁的计数器为0时,锁才会真正被释放。
- 当前线程获得锁后,如果没有显式地释放锁,那么锁会一直被当前线程持有,直到当前线程退出或抛出异常,此时锁才会被自动释放。
- 如果当前线程在获得锁之前被中断,那么该方法会抛出InterruptedException异常。
- lock方法是可重入的,即同一个线程可以重复获得已经持有的锁,不会造成死锁。
acquire
AbstractQueuedSynchronizer.java
/**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
//尝试获取锁
if (!tryAcquire(arg) &&
//addWaiter:将线程封装到 Node 节点并添加到队列尾部
//acquireQueued查看当前排队的 Node 是否在队列的前面,如果在前面,尝试获取锁资源。如果没在前面,线程进入到阻塞状态。
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
在公平锁中,tryAcquire方法会先查看state是否为0,再看是否有线程排队,若有就直接去排队。非公平锁的tryAcquire会先查看state是否为0,若为0就尝试修改state来获取锁。如果state为1,还会额外检查一下此次获取锁是否为可重入操作,若是,就直接获取锁。
addWaiter会在线程没有拿到锁资源时(tryAcquire返回true时),将当前线程封装为Node对象,把这个Node对象加入AQS的等待队列中去。
acquireQueued会检查当前线程是否排在队伍前面,若是就尝试获取锁,若长期未获得锁,就挂起当前线程。
tryAcquire
tryAcquire() 方法分公平锁和非公平锁两套实现,主要做了两件事:
如果 AQS 当前 state 为 0,尝试获取锁资源。
如果 AQS 当前 state 不为 0,查看是否是可重入操作。
公平锁
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取AQS当前state值
int c = getState();
// 判断state是否为0,为0则代表当前没有线程持有锁
if (c == 0) {
// 首先判断是否有线程在排队,如果有,tryAcquie()方法直接返回false
// 如果没有,则尝试获取锁资源
if (!hasQueuedPredecessors() &&
//将state设置为acquires
compareAndSetState(0, acquires)) {
//将当前线程设置为锁持有者线程
setExclusiveOwnerThread(current);
return true;
}
}
// 如果state != 0,则代表有线程持有锁资源
// 判断占有锁的线程是不是当前线程,如果是,则进行可重入操作
else if (current == getExclusiveOwnerThread()) {
// 可重入state的预期值
int nextc = c + acquires;
// 检查锁重入是否超过最大值,二进制第一位表示符号
//01111111 11111111 11111111 11111111 超过最大值为负值
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
非公平锁
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
非公平锁 //获取当前线程
final Thread current = Thread.currentThread();
//aqs的state状态值
int c = getState();
//如果state == 0,说明没有线程占用着当前的锁资源
if (c == 0) {
// CAS直接尝试获取锁资源,直接抢锁,不管有没有线程在队列中
if (compareAndSetState(0, acquires)) {
//将当前线程设置为锁持有者线程
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;
}
公平锁比非公平锁多了一步操作,就是当锁空闲时,查看一下是否有线程在排队,再决定是否尝试获取锁;非公平锁则不管是否有线程在排队,直接去抢锁。
addWaiter
AbstractQueuedSynchronizer.java
没有拿到锁资源时,当前线程就要被封装为Node对象并排队,会调用addWaiter方法。此方法在AQS中实现,因为要对AQS中的队列(双向链表)进行操作。
unlock
释放锁,将state从正整数更改为0,并且会将AQS中队列中挂起的线程唤醒(会有unpark方法)允许其他线程获得这个锁。
/**
* Attempts to release this lock.
*
* <p>If the current thread is the holder of this lock then the hold
* count is decremented. If the hold count is now zero then the lock
* is released. If the current thread is not the holder of this
* lock then {@link IllegalMonitorStateException} is thrown.
*
* @throws IllegalMonitorStateException if the current thread does not
* hold this lock
*/
public void unlock() {
sync.release(1);
}
在内部,它调用了同步对象sync的release方法,该方法会释放一个锁定计数器,允许其他线程获得锁。
注意:调用unlock方法的线程必须持有锁,否则会抛出IllegalMonitorStateException异常。
release
AbstractQueuedSynchronizer.java
释放锁 release方法(位于AQS)
/**
* Releases in exclusive mode. Implemented by unblocking one or
* more threads if {@link #tryRelease} returns true.
* This method can be used to implement method {@link Lock#unlock}.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryRelease} but is otherwise uninterpreted and
* can represent anything you like.
* @return the value returned from {@link #tryRelease}
* release利用tryRelease先进行释放锁,tryRealse是由子类实现的方法,可以确保线程是获取到锁的,并进行释放锁,
* unparkSuccessor主要是利用LockSupport.unpark唤醒线程;
*/
public final boolean release(int arg) {
//尝试释放锁,这个方法是由子类实现的方法
if (tryRelease(arg)) {
Node h = head;
//头节点不为如果节点状态不是CANCELLED,也就是线程没有被取消,也就是不为0的,就进行唤醒
if (h != null && h.waitStatus != 0)
//唤醒线程
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease
tryRelease在AQS中只抛出了一个异常,Sync对此方法进行了重写
该方法的作用是将锁的状态值减去传入的参数releases,并检查当前线程是否持有锁。如果当前线程不是持有锁的线程,则抛出IllegalMonitorStateException异常。如果锁的状态值为0,则表示该锁已经被完全释放,此时将锁的拥有者设置为null,并将锁的状态值设置为c,即0。如果锁的状态值不为0,则将锁的状态值设置为c。最后,如果锁已经完全释放,则返回true,否则返回false。
需要注意的是,在使用ReentrantLock时,应该使用try-finally块来确保锁的释放,避免出现死锁等问题。
//这个方法主要是确保了当前线程持有的锁,不是抛出异常,确保线程一定获取到锁的线程,并进行响应的释放锁:
protected final boolean tryRelease(int releases) {
//将线程的state计时器减-1,state为0代表没有线程持有锁,大于0则代表有线程持有锁了
int c = getState() - releases;
// 如果释放锁的线程不是占用锁的线程,抛异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 是否成功的将锁资源释放
boolean free = false;
//没有线程持有锁
if (c == 0) {
// 如果state = 0,代表成功释放锁资源
free = true;
//将记录持有线程的变量置为空
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}