并发编程工具 - ReentrantLock和Condition的管程模型、ConditionObject源码分析

目录

ReentrantLock存在的原因

ReentrantLock的结构和核心方法分析

ReentrantLock与ConditionObject实现原理

await

addConditionWaiter【添加一个waiter的Node到当前的条件链表】

fullyRelease【确保释放锁】

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;
}

当前只有一个节点,再看看上面的代码,当前的节点状态:

 

唤醒线程之后

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值