ReentrantLock
ReentranLock是并发包中提供的基于AQS实现的一个可重入锁,ReentranLock提供公平锁和非公平锁两种锁。
ReentrantLock和Syncronized的异同?
相同:都是同步锁,可重入锁。
不同:
1、Syncronized时JVM实现的,ReentrantLock是JDK(java类)实现的。
2、ReentrantLock需要配合try/finally方法进行使用。
3、ReentrantLock减少操作系统内核间的切换,提高了效率,不过Syncronized在jdk1.6之后进行了优化,效率也不低。
4、ReentrantLock是可以等待中断的,Syncronized不可以中断。
5、ReentrantLock提供公平锁和非公平锁,Syncronized只是非公平锁。
6、ReentrantLock的API更丰富,可扩展性更好。
核心内部类Sync
前面我们说过ReentranLock是基于AQS实现的,当时它本身并没有继承AQS,而是在内部定义了一个Sync静态内部类来继承AQS,然后调用静态内部类来实现的。Sync和AQS一样是一个抽象类,它本身还有两个子类分别是NonfairSync(非公平锁)、FairSync(公平锁)。之前我们将AQS的时候就说过,AQS提供了tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared四个方法给子类覆写,在这里Sync和它的子类分别覆写了tryRelease、tryAcquire方法,详细可以看代码,关于代码的详细分析下文会讲到,这里只是有个大概的认识。
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
// 提供给子类覆写
abstract void lock();
// 非公平锁,尝试获取锁,后面会详细介绍
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;
}
// ......
}
NonfairSync
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
// 从这里我们可以看到,非公平锁会首先尝试去获取锁,如果获取到锁,就不再排队
// 而公平锁则每次都需要去排队(即调用acquire方法)
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
FairSync
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
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()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
ReentrantLock源码分析
锁类型
从构造函数中我们可以看到,ReentrantLock默认是公平锁,可以通过构造函数传参选择是公平锁还是非公平锁。
public ReentrantLock() {
sync = new NonfairSync();
}
// true创建公平锁,false创建非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
获取锁
lock()方法
我们看到它调用的是Sync类的的lock方法,这个方法是提供给子类覆写的抽象方法,因此我们需要分别看两个子类的lock方法。
public void lock() {
sync.lock();
}
非公平锁的lock()方法
final void lock() {
// 通过CAS修改AQS中state的状态,如果成功则表示当前线程获取到锁,将锁的所有线程设置为当前线程。
// 修改失败则尝试去获取锁。
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
公平锁的lock()方法
final void lock() {
// 尝试去获取锁。
acquire(1);
}
关于AQS的acquire方法这里不做详细分析,感兴趣的可以去看我的这篇博客。我们AQS提供了tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared四个方法给子类覆写。在acquire方法中调用了tryAcquire方法,下面我就Sync两个子类的tryAcquire进行详细分析。
// aqs的acquire方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
非公平锁的tryAcquire()方法
protected final boolean tryAcquire(int acquires) {
// 我们看到非公平锁的tryAcquire直接调用了Sync中的nonfairTryAcquire方法
return nonfairTryAcquire(acquires);
}
abstract static class Sync extends AbstractQueuedSynchronizer {
// ......
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 获取锁的状态
int c = getState();
// 如果状态等于0,表示锁可用,通过CAS尝试修改为acquires
// acquires是我们前面调用AQS的acquire方法时闯入的参数,在这里默认1
// 如果修改成功表示获取到锁,返回ture
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 如果state不等于0,将当前线程和持有锁的线程进行比较,如果相同则state的值加acquires,这里相当于加1
// 这个判断是实现可重入锁的关键,如果没有这个判断,那么同一个线程再次获取锁的时候会陷入等待,这显然是不对的
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;
}
// ......
}
公平锁的tryAcquire()方法
static final class FairSync extends Sync {
final void lock() {
// 公平锁直接调用AQS中的aquire方法,排队获取
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 如果锁的状态等于0,并且队列中没有排队的节点,最后CAS修改state状态为1成功,那么表示获取到锁,设置当前线程为持有锁的线程并返回true
if (c == 0) {
// hasQueuedPredecessors这个方法主要是判断当前线程需不要去队列中排队获取锁,如果不需要则尝试修改锁的state状态,即尝试获取锁
// 这里要注意hasQueuedPredecessors方法被取反了,即返回false则尝试去加锁
// 即队列未初始化,或者排队的第一个线程和当前线程相同则不需要尝试获取锁,如果加锁失败则进入下一个 else if
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;
}
}
AQS的hasQueuedPredecessors方法
这个方法主要是判断当前线程需不要去队列中排队获取锁。在该方法中队列未初始化,或者排队的第一个线程和当前线程相同则不需要尝试获取锁。
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
// 判断队列中是否有等待节点,此处分为三种情况
// 1、队列未初始化,头节点和尾节点都为null,所以返回fasle,队列中没有等待的节点,当然不需要排队
// 2、队列以初始化,头节点的后续节点为空,此时s.thread肯定为null,所以s.thread != Thread.currentThread()条件不成立,即表示队列中只有一个节点,此时我们要注意的是我们的头节点是持有锁的节点,如果队列中只有一个头节点,那么表示队列中没有等待的节点,需要排队
// 3、队列以初始化,头节点后续节点不为空,即队列中不止一个节点,若后续节点的线程不等于当前线程,即比较排队的第一个节点和当前节点是否相同,是则不需要排队尝试获取锁
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
公平锁和非公平锁获取锁的区别是什么?
非公平锁在进入队列排队之前会尝试去修改AQS的state状态为1从而获取锁,如果修改成功则表示获取到锁,失败则进入队列排队。因为我们知道当AQS的state等于0的时候表示锁可用,所以修改成功就表示获取到锁,而不需要通过调用AQS的acquire方法去排队获取锁。而公平锁只有在队列未初始化或排队的第一个节点的线程和当前线程相同(可重入锁)时才会去尝试获取锁,否则直接进入队列排队获取锁
释放锁
公平锁和非公平锁的释放方法都是一样的。
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
// 尝试释放锁,成功、头节点不为空并且头节点状态不等于0,则唤醒后续节点
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
我们重点来分析Sync中的release方法。
protected final boolean tryRelease(int releases) {
// 将state减去release值,其实state状态记录了有多少个线程持有锁,在ReentrantLock中releases为1,state的状态默认为0。
// 在这里可能有个疑问,state是表示锁是否可用的,当state==0表示锁可用,那么为什么state可以被多个线程持有,state的值为什么不是1?
// 注意这里的多个线程指的是可重入锁,我们在同一个线程中可能多次获取同一个锁,每获取一次就state就加1,那么在释放的时候同样也要减1,直到state等于0时表示锁可用。
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// state等于0,表示锁被释放了,需要将锁的持有线程设置为null
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
参考博客:
https://segmentfault.com/a/1190000020521754?utm_source=tag-newest