多线程——Condition的介绍以及用法

之前总结了wait和notify的用法,今天总结一下condition的用法。

Condition介绍:

关键字synchronize可以与wait()和nitify()方法相结合实现实现等待/通知模式,类ReentrantLock也可以实现同样的功能,但需要借助condition对象。condition类是在JDK5中出现的技术,使用他有更好的灵活性,比如可以实现多路通知功能,也就是在一个Lock对象里可以创建多个condition实例,线程对象可以注册在指定的condition中从而选择性的进行线程通知,在调度线程上更加灵活。

在使用notify()/notifuAll()方法进行通知时,被调度的线程却是由JVM随机选择的。但使用ReentrantLock结合condition类是可以实现上面讲的“选择性通知”,这个功能是非常重要的,而且在condition类中默认提供的。

synchronize就相当于整个Lock对象中只有一个单一的condition对象,所有的线程都注册在它一个对象上。线程开始notifyAll()时,需要通知所有的WAITING线程,没有选择权,会出现相当大的效率问题。

  • Condition是个接口,基本的方法就是await()和signal()方法

  • Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()

  • 调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用

  • Conditon中的await()对应Object的wait();

  • Condition中的signal()对应Object的notify();

  • Condition中的signalAll()对应Object的notifyAll()。

等待/通知模式:

demo如下:
多线程——Condition的介绍以及用法
多线程——Condition的介绍以及用法
多线程——Condition的介绍以及用法

运行结果:
多线程——Condition的介绍以及用法
从运行结果来看,实现了一个最简单的等待/通知模式。

实现通知部分线程:

多线程——Condition的介绍以及用法

两个线程类:
多线程——Condition的介绍以及用法
多线程——Condition的介绍以及用法

mian函数如下:
多线程——Condition的介绍以及用法

运行结果如下:
多线程——Condition的介绍以及用法
从运行结果看出来,两个线程最终只唤醒了线程A,线程B没有被唤醒,实现了使用condition通知部分线程。

还可以使用condition实现生产者/消费者模式,有兴趣的可以自己去查查看。

Condition实现分析

Condition 是一个接口,由ConditionObject 类实现,并且它还是AQS(AbstractQueuedSynchronizer)的内部类。
多线程——Condition的介绍以及用法
从源码中可以看到,每个Condition对象都包含有一个FIFO队列,等待队列和同步队列一样,使用的都是同步器 AQS 中的节点类 Node,同样拥有首节点和尾节点。

await()方法:

调用 Condition 的 await() 方法会使线程进入等待队列,并释放锁,线程状态变为等待状态。
多线程——Condition的介绍以及用法
分析上述方法的大概过程:

  1. 先将当前线程包装创建为节点。
  2. 释放当前线程占有的锁,
  3. while循环判断当前节点是否存在队列中,
    3.1 如果没有则阻塞,继续while判断
    3.2如果存在,则退出while循环,执行下面的代码
  4. 此时说明节点已经在同步队列中,调用acquireQueude方法,重新开始竞争锁,
  5. 得到锁后返回,退出该方法。

signal()方法:
调用 Condition 的 signal() 方法,可以唤醒等待队列的首节点,唤醒之前会将该节点移动到同步队列中。
多线程——Condition的介绍以及用法
分析上述方法大概过程:
1.先判断当前线程是否有锁,
2.然后对首节点调用doSignal()方法。

多线程——Condition的介绍以及用法
1.修改首节点,直到获取到为取消的节点,
2.调用transferForSignal方法。

多线程——Condition的介绍以及用法
调用enq方法,节点加入队列,满足条件后调用LockSupport唤醒线程

(我的天,快看不懂了。)

signalAll()方法:
多线程——Condition的介绍以及用法

将等待队列中的全部节点移动到同步队列中,调用signalAll()方法,循环来唤醒每一个等待队列中的节点,直到 first 为 null 时,停止循环,并唤醒每个节点的线程。

总结:

整体就是调用condition的await()方法后,会将当前线程加入到等待队列中,然后释放锁,然后循环判断节点是否在同步队列中,再获取锁,否则一直阻塞。
调用signal()方法后,先判断当前线程是否有锁,然后调用doSignal()方法,并唤醒线程,被唤醒的线程,再调用acquireQueude()方法,重新开始竞争锁,得到锁后返回,退出该方法。