ConditionObject

ConditionObject

1.1 ConditionObject的介绍&使用

像synchronized提供了wait和notify的方法实现线程在持有锁时,可以实现挂起,已唤醒的操作

ReentrantLock也拥有这个功能。

ReentrantLock提供了await和signal方法实现类似的wait和notify的功能

想执行await或者是sinal就必须持有lock锁的资源

public class IConditionObject {
    public static void main (String[] args) throws InterruptedException {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        new Thread(() -> {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " 获取到锁资源并await挂起线程并释放锁资源");
            try {
                Thread.sleep(10000);
                condition.await();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + " 线程被唤醒,持有锁资源");
        }, "lock").start();
        Thread.sleep(1000);
        lock.lock();
        System.out.println(Thread.currentThread().getName() + " 拿到锁资源,子线程执行await方法");
        condition.signal();
        System.out.println(Thread.currentThread().getName() + " 执行 signal,主线程唤醒await的线程");
        lock.unlock();
  
        System.out.println("=====================================");

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " wait ~");
            synchronized (IConditionObject.class){
                try {
                    Thread.sleep(3000);
                    IConditionObject.class.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + " 被唤醒");
            }
        }, "sync").start();

        Thread.sleep(1000);
        synchronized (IConditionObject.class){
            System.out.println(Thread.currentThread().getName() + " notify");
            IConditionObject.class.notify();
        }
    }
}

1.2 Condition的构建方式&核心属性

发现在通过lock锁对象执行newCondition方法时,本质就是直接new的AQS提供的ConditionObject对象

public Condition newCondition() {
    return sync.newCondition();
}

其实lock锁中可以有多个Condition对象

在对Condition1进行操作时,不会影响到Condition2的单向链表

你可以发现ConditionObject中,只有两个核心属性

/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;

虽然Node对象都有prev和next,但是在ConditionObject中式不会使用这两个属性的,只要在Condition队列中,这两个属性都是null,在ConditionObject中只会使用nextWaiter的属性实现单向链表的效果

1.3 Condition的await方法分析

持有锁的线程在执行await方法后会做几个操作

  • 判断线程是否中断,如果中断了,什么都不做
  • 没有中断,就将当前线程封装为Node添加到Condition的单向链表中
  • 一次性释放掉锁资源
  • 如果当前线程没有在AQS队列,就正常执行LockSuppot.park(this)挂起线程
  • 唤醒之后,要先确认式中断唤醒还是signal唤醒,还是signal唤醒后被中断唤醒
  • 确保当前线程的Node已经在AQS中
  • 执行acquireQueued方法,等到锁资源
  • 在获取锁资源后,要确认是否在获取锁资源的阶段被中断过,如果被中断过,并且不是THROW_IE,那就确保interruptMode式REINTERRUPT。
  • 确认当前Node已经不在COndition队列中了
  • 最终根据interruptMode来决定具体做什么事情
    • 0:正常,
    • THROW_IE(-1):抛出异常
    • REINTERRUPT(1):执行线程的interrupt方法
  1. await()
public final void await() throws InterruptedException {
	// 判断线程中断标记是否为true
    if (Thread.interrupted())
		// 中断了抛出异常
        throw new InterruptedException();
	// 在挂起线程之前,先将当前线程封装为Node,并且添加到Condition队列中
    Node node = addConditionWaiter();
	// fullyRelease释放锁子资源,一次性将锁资源释放干净,并且保留重入次数
    int savedState = fullyRelease(node);
	// 中断模式~
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
		// 如果线程执行到这,说明现在被唤醒了
		// 线程可以被signal唤醒,(如果是signal唤醒,可以确认线程已经在AQS队列中)
		// 线程可以被interrupt唤醒,线程被唤醒后,没有在AQS队列中
		// 如果线程先被signal唤醒,然后线程中断了
		// checkInterruptWhileWaiting可以确认当前中如何唤醒的
		// 返回的值,有三种
		// 0:正常signal唤醒,没别的事情
		// THROW_IE(-1): 中断唤醒,并且可以确保在AQS中
		// REINTERRUPT(1): siganl唤醒,但是线程被中断了,并且可以确保在AQS中
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
	// Node一定在AQS队列
	// 执行acquireQueued,尝试在ReentrantLock中获取锁资源
	// acquireQueued返回true,代表线程在AQS中挂起时,被中断过
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
		// 如果线程在AQS队列排队时,被中断过,并且不是THROW_IE状态,确保线程interruptMode是REINTERRUPT
        interruptMode = REINTERRUPT;
	// 如果当前Node还在Condition单向链表中,需要脱离单向链表
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
	// 如果不是0
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
	// 如果interruptMode是0,说明线程在signal后以及持有锁的过程中,没被中断过,什么事都不做
}
  1. addConditionWaiter()
// 线程挂起之前,添加到Condition单向链表中
private Node addConditionWaiter() {
    // 拿到尾节点
    Node t = lastWaiter;
    // 如果尾节点有值,并且不是挂起的状态,则要清理尾节点
    if (t != null && t.waitStatus != Node.CONDITION) {
	// 如果尾节点已经取消,需要干掉取消的尾节点
        unlinkCancelledWaiters();
	// 重新获取尾节点
        t = lastWaiter;
    }
    // 构建当前线程Node,并且状态设置-2,挂起状态
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
     // 如果尾节点为null,直接将当前节点设置为头节点
    if (t == null)
        firstWaiter = node;
    else
        // 尾节点不是null,说明链表中有值,将当前节点挂在尾节点的后面
        t.nextWaiter = node;
    // 把当前节点设置为最后一个节点
    lastWaiter = node;
    // 当前节点返回
    return node;
}
  1. unlinkCancelledWaiters() 清理掉取消的节点
// 清理掉取消的尾节点
private void unlinkCancelledWaiters() {
     // 获取到头节点
    Node t = firstWaiter;
    // 临时指针
    Node trail = null;
    // 遍历链表
    while (t != null) {
	// 拿到t的next节点
        Node next = t.nextWaiter;
	// 判断t的状态不为-2,挂起的状态,说明有问题
        if (t.waitStatus != Node.CONDITION) {
	     // 就是把状态不是挂起的状态的节点跳过,移除,重新编制链表
	     // t的next指针指向null
            t.nextWaiter = null;
	     // trail 为null,代表头节点状态不是挂起的状态
            if (trail == null)
		// 将头节点指向next节点
                firstWaiter = next;
            else
		// trail 有值,说明不是头节点位置
		// 跳过有问题节点
                trail.nextWaiter = next;
		// next 为null说明,链表遍历到最后,直接位数
            if (next == null)
                lastWaiter = trail;
        }
	// 如果t的状态式-2,一切正常
        else
	    // trail 临时存储t,上一个指针
            trail = t;
	// 指针往后跳
        t = next;
    }
}
  1. fullyRelease(Node node) 一次性释放锁资源
final int fullyRelease(Node node) {
   // 标记位,释放锁资源默认失败
    boolean failed = true;
    try {
	// 拿到线程的state值
        int savedState = getState();
	// 一次性释全部锁资源
        if (release(savedState)) {
	    // 标记锁没有释放失败
            failed = false;
	    // 返回锁重入几次信息
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
	    // 如果锁资源失败了,讲节点状态设置为取消状态
            node.waitStatus = Node.CANCELLED;
    }
}
  1. boolean isOnSyncQueue(Node node) 确认Node是否在AQS队列上
// 确认Node是否在AQS队列上
final boolean isOnSyncQueue(Node node) {
    // 线程的状态为-2,肯定没有在AQS队列
    // 如果prev节点为null,肯定没有在AQS队列
    if (node.waitStatus == Node.CONDITION || node.prev == null)
	// 返回false,没有在AQS队列中
        return false;
    // 如果节点的next不为null,说明已经在AQS队列上
    if (node.next != null)
	// AQS队列上有
        return true;
    // 如果上述判断没有确认节点是否在AQS队列上,在AQS队列中寻找一波
    return findNodeFromTail(node);
}
  1. boolean findNodeFromTail(Node node) 在AQS队列中找到当前节点
// 在AQS队列中找到当前节点
private boolean findNodeFromTail(Node node) {
    // 拿到尾节点
    Node t = tail;
    for (;;) {
	 // tail是否式当前节点,如果是,说明在AQS队列
        if (t == node)
	    // 结束循环,在AQS中
            return true;
	// 如果节点为null,AQS队列中没有当前节点
        if (t == null)
	    // 结束循环,不在AQS中
            return false;
	// t 往前跳
        t = t.prev;
    }
}
  1. int checkInterruptWhileWaiting(Node node) 检查是否有中断
// 检查是否有中断,如果在发出信号之前中断,则返回THROW_IE,
// 如果在收到信号之后中断,则重新中断,如果没有中断,则为0。
private int checkInterruptWhileWaiting(Node node) {
          // 判断线程是否中断了
    return Thread.interrupted() ? 
	  // THROW_IE:代表这线程是interrupt唤醒的
	  // REINTERRUPT:代表线程是signal唤醒的,但是在唤醒后,中断了
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
	  // 0 线程是正常被signal唤醒,并且线程没有中断过
        0;
}
  1. boolean transferAfterCancelledWait(Node node) 判断线程是中断唤醒的,还是signal唤醒的
// 判断线程是中断唤醒的,还是signal唤醒的
final boolean transferAfterCancelledWait(Node node) {
    // 基于CAS将Node的状态从-2改为0
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
	// 说明是中断唤醒的,因为CAS成功了
	// 将Node添加到AQS队列中~(如果是中断唤醒,当前线程同时存在Condition链表和AQS链表中)
        enq(node);
	// 返回true,中断唤醒
        return true;
    }
    // 判断当前的Node是否在AQS中(signal唤醒的,但是可能线程还没有放到AQS队列中)
    // 等到signal方法将线程的Node扔到AQS队列后,在做后续操作
    while (!isOnSyncQueue(node))
	// 如果没有在AQS队列上,那就线程让步,稍等一会,Node放到AQS队列在处理
        Thread.yield();
   // signal唤醒的,返回false
    return false;
}
  1. reportInterruptAfterWait(int interruptMode) 判断中断模型
private void reportInterruptAfterWait(int interruptMode)
    throws InterruptedException {
     // 如果中断唤醒的await,抛出异常
    if (interruptMode == THROW_IE)
        throw new InterruptedException();
    // 如果是REINTERRUPT,signal后被中断过
    else if (interruptMode == REINTERRUPT)
	// 确认线程的中断标记位是true Thread.currentThread().interrupt();
        selfInterrupt();
}

1.4 Condition的signal方法分析

  • 确保执行signal方法的是持有锁的线程
  • 脱离Condition的队列
  • 将Node状态从-2改为0
  • 将Node添加到AQS队列
  • 为了避免当前Node无法在AQS队列正常唤醒做了一些判断和操作
  1. signal()
// 线程挂起后,可以基于signal唤醒
public final void signal() {
    // 在ReentrantLock中,如果执行了signal的线程没有持有锁资源,直接抛出异常
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    // 拿到排在Condition首位的Node
    Node first = firstWaiter;
    // 有Node在排队,才需要唤醒
    if (first != null)
        doSignal(first);
}
  1. doSignal(Node first) 开始唤醒Condition中的Node的线程
// 开始唤醒Condition中的Node的线程
private void doSignal(Node first) {
    do {
       // 获取到第二个节点,并且将第二个节点设置为头节点
        if ( (firstWaiter = first.nextWaiter) == null)
	    // 说明就一个节点在Condition队列中,那么直接将firstWaiter,lastWaiter设置为空
            lastWaiter = null;
        // 如果还有nextWaiter节点,以为当前节点要被唤醒了,脱离整个Condition队,将nextWaiter设置为空
        first.nextWaiter = null;
	// transferForSignal返回true,一切正常,推出while循环
    } while (!transferForSignal(first) &&
	     // 如果后续节点还有,往后面继续唤醒,如果没有结束循环
             (first = firstWaiter) != null);
}
  1. boolean transferForSignal(Node node) 准备开始唤醒在Condition中排队的Node
// 准备开始唤醒在Condition中排队的Node
final boolean transferForSignal(Node node) {
    // 将在Condition队列中的Node状态从-2,改为0,代表要扔到AQS队列中
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
	// 如果失败了,说明在signal之前应当是线程被中断了,从而被唤醒了。
        return false;
    // 如果正常从-2改为0,这就是要从Condition队列扔到AQS中去
    // 将当前Node扔到AQS队列,返回的p是当前Node的prev
    Node p = enq(node);
    // 前继节点的状态
    int ws = p.waitStatus;
     // 如果前继节点状态不是取消的状态
     // 将前继节点状态改为-1
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
	// 如果prev节点已经取消了,可能导致当前节点永远不会被唤醒,立即唤醒当前节点
	// 基于acquireQueued方法,让当前节点找到一个正常前继节点,并挂起线程
	// 如果prev节点正常,但是CAS修改prev节点失败了。证明prev节点因为并发原因导致状态改变。
	// 还是为了避免当前节点无法被正常唤醒,提前唤醒当前线程,基于acquireQueued方法,让当前节点找到一个正常的prev节点,并挂起线程
        LockSupport.unpark(node.thread);
    // 返回true
    return true;
}

1.5 Condition的awaitNanos&signalAll方法分析

awaitNanos:仅仅是在await方法的基础上,做了一些的改变,整体的逻辑思想都是一样的。

挂起来线程时,传入要阻塞的时间,时间到了,自动唤醒,走添加到AQS队列的逻辑

// await指定时间,时间到了自动醒
public final long awaitNanos(long nanosTimeout)
        throws InterruptedException {
    // 判断是否中断
    if (Thread.interrupted())
        // 如果中断了抛出异常
        throw new InterruptedException();
    // 添加到Condition单向链表中
    Node node = addConditionWaiter();
    // 一次性释放掉锁
    int savedState = fullyRelease(node);
    // 计算什么时间结束
    final long deadline = System.nanoTime() + nanosTimeout;
    // 中断模型
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        // nanosTimeout的时间小于等0,直接结束
        if (nanosTimeout <= 0L) {
            // 尝试从等待状态修改成0,并加入到AQS队列中
            transferAfterCancelledWait(node);
            break;
        }
        // nanosTimeout的时间大于1000纳米时,才可以挂起
        if (nanosTimeout >= spinForTimeoutThreshold)
            // 大于,正常挂起线程
            LockSupport.parkNanos(this, nanosTimeout);
        // 检查节点是否被中断唤醒
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
        // 计算出剩余挂起时间
        nanosTimeout = deadline - System.nanoTime();
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null)
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
    return deadline - System.nanoTime();
}

signalAll方法,这个方法一看就懂,之前signal是唤醒1个,这个是全部唤醒

// do-while的形式,将Condition单向链表中的所有Node,全部唤醒并扔到AQS队列中
private void doSignalAll(Node first) {
    // 将头尾节点都只为空
    lastWaiter = firstWaiter = null;
    do {
        // 拿到next节点的引用
        Node next = first.nextWaiter;
        // 断开当前Node的nextWaiter
        first.nextWaiter = null;
        // 修改Node状态,扔到AQS队列,是否唤醒
        transferForSignal(first);
        // 指向下一个节点
        first = next;
    } while (first != null);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值