多线程学习(五)并发工具

Condition

基本认识

  • Condition是在java1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,阻塞队列实际上是使用了Condition来模拟线程间协作。

基本用法

ConditionAwait

public class ConditionAwait implements Runnable{
	
	private Lock lock;
	private Condition condition;
	
	public ConditionAwait(Lock lock,Condition condition) {
		this.lock = lock;
		this.condition = condition;
	}

	@Override
	public void run() {
		try {
			lock.lock();//先上锁
			System.out.println("Condition开始!!");
			condition.await();//中断
			System.out.println("Condition结束!!");
		} catch (InterruptedException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}finally {
			lock.unlock();//最后再释放锁
		}
		
	}

}

ConditionSignal

public class ConditionSignal implements Runnable{
	
	private Lock lock;
	private Condition condition;
	
	public ConditionSignal(Lock lock,Condition condition) {
		this.lock = lock;
		this.condition = condition;
	}

	@Override
	public void run() {
		try {
			lock.lock();//先上锁
			System.out.println("ConditionSignal开始!!");
			condition.signal();//唤醒
			System.out.println("ConditionSignal结束!!");
		} catch (Exception e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}finally {
			lock.unlock();//最终都会释放锁
		}
		
	}

}

最终运行

public class app {

	public static void main(String[] args) {
		Lock lock = new ReentrantLock();//创建重入锁
		Condition condition = lock.newCondition();
		new Thread(new ConditionAwait(lock, condition)).start();
		new Thread(new ConditionSignal(lock, condition)).start();		

	}

}

结果:
在这里插入图片描述
总结:
通过这个案例简单实现了 wait 和 notify 这个两个方法的功能,当调用 await 方法后,当前线程会释放锁并等待,而其他线程调用 condition 对象的 signal 或者 signalall 方法通知并被阻塞的线程,然后自己执行 unlock 释放锁,被唤醒的线程获得之前的锁继续执行,最后释放锁。所以,condition 中两个最重要的方法,一个是 await ,一个是 signal 方法

  • await: 把当前线程阻塞挂起
  • signal:唤醒阻塞的线程

源码分析

  1. 第一步如果要调用Condition的话,就需要获得 lock 的锁,两个线程就会使用到AQS同步队列
    在这里插入图片描述

  2. 调用 Condition 的 await 方法会调用AQS里面的await()方法。
    在这里插入图片描述

    • 源码运行到 Node node = addConditionWaiter()
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
    • 运行到 int savedState = fullyRelease(node) ,目的是为了释放当前锁,得到锁的状态去唤醒AQS队列中的线程。
      在这里插入图片描述
      在这里插入图片描述
    • 运行到while循环,判断这个节点是否在 AQS 队列上
      在这里插入图片描述
      在这里插入图片描述
    • 运行到 LockSupport.park(this),挂起当前线程。
      在这里插入图片描述
  3. 线程B 被唤醒,然后执行到 signal()方法。
    在这里插入图片描述

    • 第一步先做一个判断,判断当前线程是否是拿到锁的线程,如果不是就报错
    • 第二步拿到 condition 队列中的第一个结点,如果有就开始执行 doSignal()方法
      在这里插入图片描述
      • 第一步先将 Condition 队列的当前节点移除(ThreadC是假设的)
        在这里插入图片描述
      • 第二步执行 transferForSignal()方法,该方法先是 CAS 修改了节点状态,如果成功,就将这个节点放到 AQS 队列中,然后唤醒这个节点上的线程。此时,那个节点就会在 await 方法中苏醒
        在这里插入图片描述
        • 第一步更新节点的状态为0,如果更新失败,只有一种可能就是节点被CANCELLED了
        • 第二步调用enq,把当前节点添加到AQS队列。并且返回按当前节点的上一个节点,也就是原tail节点
          在这里插入图片描述
        • 第三步进行判断,如果上一个节点的状态被取消了, 或者尝试设置上一个节点的状态为 SIGNAL 失败了(SIGNAL 表示: 他的 next 节点需要停止阻塞),
        • 第四步如果失败了,唤醒结点上的线程
    • 执行完 doSignal 以后,会把 condition 队列中的节点转移到 aqs 队列上, 这个时候会判断 ThreadA 的 prev 节点也就是 head 节点的 waitStatus 如果大于 0 或者设置 SIGNAL 失败,表示节点被设置成了 CANCELLED 状态 。 这个时候会唤醒ThreadA 这个线程 。 否则就基于 AQS 队列的机制来唤醒,也就是等到 ThreadB 释放锁之后来唤醒 ThreadA
  4. 唤醒之后回到 await()方法

  • 总结流程:
    在这里插入图片描述
  1. 第五步又会回到原来 await()方法
    在这里插入图片描述

    1. 执行 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) 判断是否被中断
      • 这里需要注意的地方是,如果第一次 CAS 失败了,则不能判断当前线程是先进行了中断还是先进行了 signal 方法的调用,可能是先执行了 signal 然后中断,也可能是先执行了中断,后执行了 signal ,当然,这两个操作肯定是发生在 CAS 之前。这时需要做的就是等待 当前线程的 node被添加到 AQS 队列后,也就是 enq 方法返回后,返回false 告诉 checkInterruptWhileWaiting 方法返回 REINTERRUPT ( 1),后续进行重新中断。
      • 简单来说,该方法的返回值代表当前线程是否在 park 的时候被中断唤醒,如果为 true 表示中断在 signal 调用之前, signal 还未执行 ,那么这个时候会根据 await 的语义,在 await 时遇到中断需要抛出 interruptedException ,返回 true 就是告诉 checkInterruptWhileWaiting 返回 (THROW _IE 1) 。如果返回 false ,否则表示 signal 已经执行过了 ,只需要重新响应中断即可
        在这里插入图片描述
    2. 执行到 if (acquireQueued(node, savedState) && interruptMode != THROW_IE) 这一步为了恢复线程的锁,如果没有中断的话
      • acquireQueued 方法是让当前被唤醒的节点 ThreadA 去抢占同步锁 。并且要恢复到原本的重入次数状态。
    3. 执行到 if (interruptMode != 0) reportInterruptAfterWait(interruptMode);这一步主要是根据 checkInterruptWhileWaiting 方法返回的中断标识来进行中断上报 。如果是 THROW_IE ,则抛出中断异常,如果是 REINTERRUPT ,则重新响应中断

总结

  • 阻塞: await() 方法中,在线程释放锁资源 之后,如果节点不在 AQS 等待队列,则阻塞当前线程,如果在等待队列,则自旋等待尝试获取锁
  • 释放: signal() 后,节点会从 condition 队列移动到 AQS等待队列,则进入正常锁的获取流程
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值