目录
ReentrantLock与ConditionObject实现原理
addConditionWaiter【添加一个waiter的Node到当前的条件链表】
signal、signalAll【将等待队列的节点移动到CLH,所以最好调用signAll】
ReentrantLock存在的原因
ReentranLock作为AQS的【独占模型】重要实现的可重入锁,继承自juc Lock接口,基于ReentrantLock + ConditionObject实现了管程模型,相当于Synchronized的ObjectMonitor + Object类(的wait、notify、notifyAll方法)的MESA管程模型。那么在JVM中已经了一种管程模型,为什么还需要再去实现一种呢?如果是基于synchronized的性能诟病,那么在Jdk6之后基于【无锁、偏向锁、轻量级锁、重量级锁升级】的优化(参见:并发编程基础 - synchronized锁优化),原因在于前面提到的大神 Conffman理论提出的满足线程死锁的四个必要条件(只要破其中一个就不可能死锁),其中一个是满足不可抢占的条件,但是synchronized在申请资源的时候,如果获取就会一直阻塞在队列中(线程切换为阻塞状态),则就没有超时也不能释放资源了。考虑的因素都在ReentrantLock的父类接口juc Lock接口中定义:
1)、能够响应中断【Lock#lockInterruptibly】
2)、支持超时【Lock#tryLock(long time, TimeUnit unit)】
3)、非阻塞的获取锁【Lock#tryLock】
具体看看Lock接口中定义的规范:
public interface Lock {
/**
* Acquires the lock.
*/
void lock();
/**
* Acquires the lock unless the current thread is
* {@linkplain Thread#interrupt interrupted}.
*/
void lockInterruptibly() throws InterruptedException;
/**
* Acquires the lock only if it is free at the time of invocation.
*
* <p>A typical usage idiom for this method would be:
* <pre> {@code
* Lock lock = ...;
* if (lock.tryLock()) {
* try {
* // manipulate protected state
* } finally {
* lock.unlock();
* }
* } else {
* // perform alternative actions
* }}</pre>
*/
boolean tryLock();
/**
* Acquires the lock if it is free within the given waiting time and the
* current thread has not been {@linkplain Thread#interrupt interrupted}.
*/
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
/**
* Releases the lock.
*/
void unlock();
/**
* Returns a new {@link Condition} instance that is bound to this
* {@code Lock} instance.
*/
Condition newCondition();
}
ReentrantLock的结构和核心方法分析
ReentrantLock只有一个属性Sync内部类,并且实现了AQS模板类
public class ReentrantLock implements Lock, java.io.Serializable {
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
/**
* 默认构造创建内部类NonfairSync,非公平锁
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* 也提供了可以控制创建公平或非公平锁
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
}
Sync
abstract static class Sync extends AbstractQueuedSynchronizer {
/**
* 实现定义的{@link Lock#lock},默认实现是非公平锁
*/
abstract void lock();
/** 默认实现了AQS的tryAcquire */
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;
}
// 公平和非公平锁,实现AQS的tryRelease都相同
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;
}
/** 实现AQS的isHeldExclusively */
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();
}
/** 直接创建AQS的ConditionObject */
final ConditionObject newCondition() {
return new ConditionObject();
}
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {
return getState() != 0;
}
/**
* readObject看似无厘头,但是梳理过Daug Lea对AQS设计就知道,AQS默认没有序列化控制,被终止后
* 没有,没有执行完成的队列任务就会被丢弃,重启后队CLH列为空,需要子类自己实现
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
1)、AQS定义了tryRelease、tryAcquire、isHeldExclusively并且默认会抛出UnsupportedOperationException异常,需要AQS实现。
2)、ReentrantLock默认构造实现的是非公平锁,所以这里也默认实现非公平锁子类的nonfairTryAcquire方法,公平锁的tryAcquire方法由子类自己实现;tryRease公用。
3)、readObject看似无厘头,但是梳理过Daug Lea对AQS设计就知道,AQS默认没有序列化控制,被终止后没有,没有执行完成的队列任务就会被丢弃,重启后队CLH列为空,需要子类自己实现。详细见:并发编程理论 - AQS的设计意图和扩展用法
4)、FairLock和UnfireLock都是final修饰,也是在并发编程理论 - AQS的设计意图和扩展用法中设计的,而lock方法基本都是调用AQS的acquire(1) 进行实现,之前也提到了state由实现类自己定义含义,所以这里都使用1
ReentrantLock与ConditionObject实现原理
并发编程基础 - synchronized使用场景和等待唤醒机制的正确姿势知道Object的wait、notify、notifyAll方法需要在synchronized中进行调用,否则会抛出java.lang.IllegalMonitorStateException异常;这是由MESA管程模型定义的。那么基于ReentrantLock和Condition实现的MESA管程模型,则允许多个条件队列存在(至于怎么定义满足条件队列就看代码怎么编写【单向链表】,再加入CLH双向队列,等待被唤醒执行任务)。看看ReentrantLock#newCondition
final ConditionObject newCondition() {
return new ConditionObject();
}
之前的Guarded Suspension [受保护的暂停] 模式中就是用ReentrantLock+ConditionObject进行实现的等待唤醒模式,来分析实现时的细节原理:
/**
* Guarded Suspension [受保护的暂停] 模式; 等待唤醒机制
*
* Thread-1 ---> wait
* obj = null;
* <---- Thread-2
* obj = XXX;
* signalAll;
* Thread-1 <---
*
* @author kevin
* @date 2020/8/5 23:13
* @since 1.0.0
*/
public final class GuardedObject<T> {
/** 受保护的对象 */
T object;
/** 创建公平锁 */
private final ReentrantLock lock = new ReentrantLock(true);
private final Condition done = lock.newCondition();
final int timeout = 5;
/**
* 获取受保护的对象
* @param predicate 条件
* @return 受保护对象
*/
public T get(Predicate<T> predicate) {
lock.lock();
try {
// MESA管程推荐的写法
while (!predicate.test(object)) {
done.await(timeout, TimeUnit.MILLISECONDS);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
// 返回非空的受保护对象
return object;
}
/**
* 事件通知方法
* @param object 包含对象【改变状态值】
*/
public void onChange(T object) {
lock.lock();
try {
this.object = object;
done.signalAll();
} finally {
lock.unlock();
}
}
}
在try final中进行lock和unlock操作,并且在其中执行ConditionObject的await、signalAll(signal),就是使用自旋判断,并将自己加入队列中并将关联好自己的线程等待别人唤醒【LockSupport#unpark(关联给别人的自己的线程)】,再调用LockSupport#park将自己的线程挂起。为了方便理解下面的源码分析,执行main方法调用上面的demo
public static void main(String[] args) throws InterruptedException {
GuardedObject guardedObject = new GuardedObject();
CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread(() -> {
// 1、执行AQS await的 addConditionWaiter(); fullyRelease(node);
guardedObject.get(Objects::nonNull); // 出自旋的条件是被保护的Object不为null
countDownLatch.countDown();
}).start();
// 线程创建一个线程,sleep一定时间,后唤醒自旋的主线程
new Thread(() -> {
try { Thread.sleep(5000L); } catch (InterruptedException e) { e.printStackTrace(); }
// 2、执行AQS的signAll方法
guardedObject.onChange(new Object());
// 3、执行await中的 自旋线程
countDownLatch.countDown();
}).start();
countDownLatch.await();
}
await
public final void await() throws InterruptedException {
if (Thread.interrupted()) // 判断是否线程被中断
throw new InterruptedException();
// 将当前线程创建Condition的节点,添加到当前的条件链表
AbstractQueuedSynchronizer.Node node = addConditionWaiter();
// 由于是独占锁(即使的共享锁也一样)当前线程一定占有锁资源,即state的拥有者。所以需要释放锁
int savedState = fullyRelease(node);
int interruptMode = 0;
// 释放完毕后,遍历AQS的队列,看当前节点是否在队列中
while (!isOnSyncQueue(node)) {
LockSupport.park(this); // 不在,那就简单了,直接将当前线程挂起
// 挂起时,还是要判断是否
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 前一篇博客分析过,以独占并且非线程中断模式获取已经存在于队列中的线程
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
addConditionWaiter【添加一个waiter的Node到当前的条件链表】
private AbstractQueuedSynchronizer.Node addConditionWaiter() {
AbstractQueuedSynchronizer.Node t = lastWaiter;
// 如果最后的条件节点被取消了,则需要剔除
if (t != null && t.waitStatus != AbstractQueuedSynchronizer.Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
// 为当前线程创建一个CONDITION类型的节点
AbstractQueuedSynchronizer.Node node = new AbstractQueuedSynchronizer.Node(Thread.currentThread(), AbstractQueuedSynchronizer.Node.CONDITION);
if (t == null) // 当前条件链表为空,则链表的firstWaiter执向当前节点,
firstWaiter = node;
else // 否则将原来的为节点Node的 nextWaiter执向当前节点
t.nextWaiter = node;
lastWaiter = node; // 最终肯定要讲当前条件链表lastWaiter 执向当前节点(到此,整个条件链接则加入了当前线程节点到最后)
return node;
}
我们看看上面的代码,当前的节点状态:
fullyRelease【确保释放锁】
final int fullyRelease(AbstractQueuedSynchronizer.Node node) {
boolean failed = true;
try { // 获取state
int savedState = getState();
// 【释放独占锁,唤醒管程的“下一个”节点线程,使用unparkSuccessor】
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = AbstractQueuedSynchronizer.Node.CANCELLED;
}
}
我们看看上面的代码,当前的节点状态:
整个await方法继续执行,就会到执行LockSupport.parkNanos(this, nanosTimeout)、LockSupport.park(一直)将当前线程挂起,等待条件满足后,将当前线程进行唤醒,即下面的signalAll、signal方法。当唤醒后,根据MESA管程模型可知,当前线程会从条件队列进入等待队列(当前就是CLH队列),即acquireQueued【以独占并且非线程中断模式获取已经存在于队列中的线程】。那么添加到CLH队列的尾部后,需要CLH的前面节点的唤醒。当然现在队列为空,则添加的节点本身就是自己。看看上面的代码,当前的节点状态:
signal、signalAll【将等待队列的节点移动到CLH,所以最好调用signAll】
/** 将等待时间最长的线程(如果存在)从等待队列添加到等待队列(CLH中有机会拥有锁)。 */
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
AbstractQueuedSynchronizer.Node first = firstWaiter;
if (first != null)
doSignal(first);
}
/** 将此条件的等待队列中的所有线程移动到拥有锁的等待队列CLH中,那么值钱被挂起的线程肯定会被执行的 */
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
AbstractQueuedSynchronizer.Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
/** 移动第一个节点 */
private void doSignal(AbstractQueuedSynchronizer.Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
/** 移动所有节点 */
private void doSignalAll(AbstractQueuedSynchronizer.Node first) {
lastWaiter = firstWaiter = null;
do {
AbstractQueuedSynchronizer.Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null); // 循环,只要第一个节点不为null,即全部链表
}
核心的方法,不论是一个节点还是地柜调用所以节点,都会执行transferForSignal方法
/** 将一个条件队列的节点,添加到等待队列中 */
final boolean transferForSignal(AbstractQueuedSynchronizer.Node node) {
// 如果节点被取消了则不能cas替换waitStatus属性,则返回失败
if (!compareAndSetWaitStatus(node, AbstractQueuedSynchronizer.Node.CONDITION, 0))
return false;
// enq之前分析过,就是讲当前等待节点,添加到CLH队尾
AbstractQueuedSynchronizer.Node p = enq(node);
int ws = p.waitStatus;
//
if (ws > 0 || !compareAndSetWaitStatus(p, ws, AbstractQueuedSynchronizer.Node.SIGNAL))
LockSupport.unpark(node.thread); // 唤醒刚加入到同步队列的线程,被唤醒之后,该线程才能从await()方法的park()中返回
return true;
}
当前只有一个节点,再看看上面的代码,当前的节点状态:
唤醒线程之后