JUC之ReentrantLock
前言
学习juc系列的可重入锁之前,可以先去看看aqs的实现,这个很关键,毕竟aqs是核心。
传送门:也是我写的嘿嘿嘿,写的很详细
学习了 synchronized的之后,面试肯定还会问到ReentrantLock
// 可以看到ReentrantLock这个类的构造函数 对FairSync,或者是NonfairSync这个类进行了一下初始化
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
而Sync 这个类呢,就是继承了AstractQueueqSynchronizer 这个类,所以说aqs 真的很重要
NonfairSync
final void lock() {
// 如果线程只有一次请求,只需要执行setExclusiveOwnerThread 方法
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
如果你看懂了aqs,那么肯定知道,实现了aqs的类,肯定需要实现一个tryAcquire(int acquires) 方法,也就是这个方法,非公平的可重入锁,来看看它怎么写的吧。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 前面的lock方法已经设置过了一次,这里再加上它,因为可能我进来的时候,上一个线程已经释放了锁了。
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 如果是同一个线程的话就对state进行增加
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;
}
看到了上面的代码 是不是觉得非公平的可重入锁,获取资源实在是太简单了 。没错就是很简单,如果你知道了彻底懂了aqs的话。
那么它为什么叫做非公平锁呢?
因为aqs中有一个addWaiter的方法,会将没有竞争到资源的线程加入到等待队列中,如果head结点释放资源了,就会去唤醒,后面存活的等待的结点。但是释放资源的同时呢,也把state置为呢0,那么新来的线程是不是执行lock 里面的compareAndSetState(0, 1),更加有优势呢,等待队列中的结点在acquireQueued方法中进行获取的时候,tryAcquire 进行会失败,并且将首节点标记位signal,然后它自己会继续挂起等待下去。
FairSync
可以看到对比非公平锁,我们这里多了一个方法hasQueuedPredecessors
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;
}
// 这个呢 就是非公平锁,加这个方法的目的是什么,我们了解了非公平锁了对吧,那么就想想公平锁为什么需要加这个方法,公平锁就是为了保证顺序对吧,保证的是什么顺序呢,其实就是等待队列的顺序,也就是clh队列中的顺序,那么看代码就可以知道,如果队列中不是只有一个结点,并且首结点的下一个结点的线程不等于当前线程的话,它是必须返回true的,如果它刚好又不是独占线程的话,那么tryAcquire这个方法就会返回false,它就只能乖乖的加入到等待队列中去了。
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
最后来看看tryRelease(int arg) 方法
这个方法的关键就是什么时候返回true,去执行unparkSuccessor方法,如果state等于0了,那么就可以返回true了。
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;
}