ReentrantLock
AQS是同步器的基础,要先了解AQS的实现
使用实例:
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
try {
while(条件判断表达式) {
condition.wait();
}
// 处理逻辑
} finally {
lock.unlock();
}
ReentrantLock可重入锁就是当前持有该锁的线程能够多次获取该锁,无需等待;
我们之前介绍过AQS,它是J.U.C包的基础,实现了同步器的主要功能。那么ReentrantLock是怎么利用AQS的?
这要从ReentrantLock的一个内部类Sync的父类说起,Sync的父类是AQS,Sync的两个实现类分别是NonfairSync和FairSync,由名字可以猜到,一个是用于实现公平锁、一个是用于实现非公平锁。
那么Sync为什么要被设计成内部类呢?这是文档里建议你使用的方法,子类应该是一个非public的内部类。为什么我想是因为安全,AQS里有很多方法,直接让ReentrantLock继承意味着拥有了操作权,容易误用,所以采用内部类实现的方法;
public void lock() {
sync.lock();
}
public void unlock() {
sync.release(1);
}
ReentrantLock的lock与unlock方法委托给了sync;
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;
}
构造器:
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
非公平锁:
在上面的实例中,使用的就是非公平锁,啥叫非公平?就是插队,举个例子:A,B两个线程,B排在A后等待被唤醒,此时A执行完了同步状态清零,唤醒B,但在这是C抢了进来,B没有争过C,也就是B被C插了队,这是不公平的;
继承AQS的子类,要实现自己的tryRelease与tryAcquire;
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
举例:A,B两个线程竞争,A成功同步状态会被设置成1,exclusiveOwnerThread(在AbstractOwnableSynchronizer中);B失败。
B会调用acquire——>tryAcquire——>nonfairTryAcquire:在该方法中又会再一次判断下同步状态因为A可能执行完了,否则会检查当前线程是否是exclusiveOwnerThread中保存的线程,是则增加同步状态次数(这里是可重入的代码实现),都不是,则放入阻塞队列(逻辑在我的AQS文章中)
nonfairTryAcquire里实现了可重入的逻辑,与exclusiveOwnerThread保存的线程相等则允许进入,增加次数。
公平锁
之前说非公平是因为存在插队,而插队发生在AQS的acquireQueued方法的for循环中
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
之前的AQS里介绍过,当前线程release将同步状态归零,这是一处被插队的机会;当在等待队列中等待的线程被head唤醒,他会再一次进行判断,就是tryAcquire,在这里就有可能被竞争的线程抢先,即插队;所以要想实现公平锁就得对tryAcquire进行更改,怎么改?不是head的next节点就不能返回true
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;
}
}
hasQueuedPredecessors就实现了这个逻辑,下一个线程首先必须是head的next指向的节点才行。
重入逻辑
在非公平锁的nonfairTryAcquire里与公平锁FairSync 的tryAcquire里都有如下重入逻辑
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}