ReentrantLock是JAVA中除synchronized外,另一种简便的管理资源锁的工具类,它还提供了Condition对象的创建,供调用者添加不同的条件队列,实现更灵活多样化的等待机制,ReentrantLock是基于AQS实现的,所以在阅读本文前需要掌握AQSJAVA并发(二)AQS,否则会看起来比较吃力。
文章目录
ReentrantLock
ReentrantLock有两个构造器,可选择公平锁FairSync
和非公平锁NonFairSync
。
private final Sync sync;
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁与非公平锁
ReentrantLock的加锁lock()
分为公平锁与非公平锁。
公平锁FairSync
和非公平锁NonFairSync
都继承至Sync
,而Sync
继承至AQS,因此,ReentrantLock是基于AQS实现的。公平锁与非公平锁的区别是lock的机制不同,而锁的释放是一致的,下方代码块截取了Sync
类的加锁相关的方法。Sync
提供了lock的抽象方法、非公平锁的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;
}
}
NonfairSync&FairSync
公平锁与非公平锁的代码块如下。
// 非公平锁
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1)) // CAS竞争资源
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
// 公平锁
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;
}
}
lock()
它们lock方法的区别就是非公平锁会先尝试CAS竞争资源,竞争失败以后才会调用acquire,而非公平锁不去竞争锁资源,直接调用acquire方法。acquire方法就是把队列加入到同步队列。
// 非公平锁的lock
final void lock() {
if (compareAndSetState(0, 1)) // CAS竞争资源
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
// 公平锁的lock
final void lock() {
acquire(1);
}
在lock
方法中,非公平锁与公平锁都会调用acquire
方法,
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
在JAVA并发(二)AQS中,介绍了AQS提供的资源共享方式,其中资源获取与释放的条件交给AQS的子类实现,而具体如何获取资源、如何释放资源则由AQS内部实现好了,因此:
Sync
及其子类FairSync、NonFairSync
只需要关心实现tryAcquire()&tryRelease()
,同时,tryAcquire()&tryRelease()
是一对独占式的资源锁的获取/释放条件,因此无论是公平锁还是非公平锁,都是独占式的。如果tryAcquire()&tryRelease()
返回false,AQS会主动通过acquireQueued
方法将当前线程加入同步队列,
tryAcquire()
从对lock()
的分析得知,tryAcquire()
用于给AQS的子类提供获取锁的灵活定义机制,如果返回false,AQS会将当前线程加入同步队列。而非公平锁的tryAcquire()
实际上是父类Sync实现的,公平锁的tryAcquire()
是FairSync
实现的。
非公平锁tryAcquire
如果资源没有被占用,尝试通过CAS占用资源,如果资源已被占用,判断是否为当前线程占用的,若是,则将state+1,即重入锁的实现。
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;
}
公平锁tryAcquire
与非公平锁唯一不同的是第5行多了一个判断!hasQueuedPredecessors()
,该方法用来判断同步队列中是否有其他线程正在等待资源,只有在同步队列中没有线程时,才会允许当前线程通过CAS竞争锁资源。
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;
}
小结
公平锁与非公平锁的区别:
- 公平锁严格遵循FIFO,获取锁资源时,会判断同步队列中是否有其他线程,没有其他线程时才会通过CAS竞争资源,否则就进入同步队列。
- 非公平锁会直接通过CAS竞争资源,两次CAS竞争都失败时才会加入同步队列。
公平锁与非公平锁都是独占锁、可重入的。
Condition条件队列
在JUC包下,只有ReentrantLock提供了newCondition接口创建Condition对象(其他的实现在同步工具类的内部类中,程序员也无法使用)。
AQS不仅有同步队列,还有条件队列(Condition),条件队列中的线程是没有权限获取资源的,只有同步队列中的线程才有机会获取资源。条件队列是严格的FIFO的,AQS支持一个同步队列、多个条件队列,通过conditon.await()
方法将当前线程加入条件队列condition,通过condition.signal()
方法从条件队列condition的头节点取出一个线程放在同步队列中,condition.signal()
将条件队列condition中所有节点均添加到同步队列中,从条件队列移动至同步队列的线程就有机会竞争锁资源了,最后锁资源的释放还是要通过调用AQS的tryRelease实现,而ReentrantLock中调用tryRelease的是unlock()
方法。
所以我们可以按照如下的例子使用条件队列。
ReentrantLock&Condition配合使用
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
public void customLock(int n) {
try {
if(n == 1) {
condition1.await(); // 将当前线程添加到条件队列condition1
} else if(n > 1) {
condition2.await(); // 将当前线程添加到条件队列condition2
} else {
condition3.await(); // 将当前线程添加到条件队列condition3
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void customUnlock(int n) {
if(n == 1) {
conditon1.signal(); // 从条件队列condition1中移动个线程至同步队列。
} else if(n >1) {
condition2.signal(); // 从条件队列condition2中移动个线程至同步队列。
} else {
condition3.signal(); // 从条件队列condition3中移动个线程至同步队列。
}
lock.unlock(); // 从同步队列释放一个
}
从上面的例子可以看出,使用condition可以很方便的将线程进行分类,根据不同的条件加入不同的条件队列。在释放时,也可以根据不同的条件决定将哪个条件队列的线程移动至同步队列,给予该线程竞争资源的资格。
Condition的原理见:JAVA并发(二)AQS
与Synchronized的区别
- ReentrantLock基于AQS实现,synchronized基于JMM内存模型中的最小原子操作lock、unlock实现(monitorenter、monitorexit指令),synchronized是对象锁,而AQS是基于队列与CAS实现的。
- ReentrantLock包含公平锁(线程严格遵循FIFO获取资源的访问权限)、非公平锁两种实现,synchronized只有非公平锁。
- ReentrantLock更灵活,由于基于AQS,所以它支持一个同步队列和多个条件队列,而synchronized只支持一个同步队列和一个条件队列(wait、notify)。