6.类似Object监视器方法的Condition接口

  在《1.有关线程、并发的基本概念》中,我们利用synchronized关键字、Queue队列、以及Object监视器方法实现了生产者消费者,介绍了有关线程的一些基本概念。Object类提供的wait的方法和notifyAll方法,与之对应的是Condition接口提供是await和signalAll。await(或wait)是让当前线程进入等待状态并释放锁,signalAll(或notifyAll)则是唤醒等待中的线程,使得等待中的线程有竞争锁的资格,注意只是资格,并不代表被唤醒的线程就一定会获得锁。

  Condition接口的具体实现还是在AbstractQueuedSynchronizer中的内部实现的——AbstractQueuedSynchronizer$ConditionObject。ConditionObject中维护了一个“等待队列”,注意这个和AQS同步器维护的“同步队列”不同。AQS所维护的同步队列是当前等待资源(同步状态)的队列,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成一个节点并加入到同步队列中,同时阻塞当前线程,当同步状态被所持有的线程释放时会将同步队列中的首节点唤醒重新获取同步状态。而每个Condition维护一个等待队列,该队列的作用是一个等待signal信号的队列。这两者之间的关系是一个协同的关系,用下图的说明它们之间的协同过程:

  1. AQS的同步队列如下图所示,一个头结点head指向队首,一个tail指向队尾,当线程调用lock()方法获取锁而未成功时,线程被构造成节点加入到队尾。(图中NodeA是同步队列的第一个节点,也就是获得同步状态的节点)

  2.NodeA调用await()方法时,NodeA从AQS同步队列中移除,自然也就释放了锁,NodeA此时被加入到Condition的等待队列中,等待signal信号,如下图所示。

  

  3.执行完第2步后,此时NodeB在同步队列中处于第一个节点位置,即获取到了锁,如果NodeB此时执行signal(或者signalAll)方法,NodeA将会从Condition等待队列中被移除即被唤醒,加入到同步队列中,此时NodeA仅仅是被唤醒有了在同步队列中争夺资源的资格,并不代表被唤醒后就立即获得锁,如下图所示。

  4. 最后NodeB在signal执行完毕后,调用unLock方法释放锁,此时NodeA处于队首,并争夺同步状态。

  以上是AQS的“同步队列”和Condition的“等待队列”之间相互协作的过程,下面从源码解析Condition的主要方法await、signal、signalAll。

 1 public final void await() throws InterruptedException{
 2     if (Thread.interrupted())    //线程被中断则抛出中断异常
 3         throw new InterruptedException();
 4     Node node = addConditionWaiter();    //将线程构造为Node节点
 5     long savedState = fullyRelease(node);    //释放锁,返回同步状态
 6     int interruptMode = 0;
 7     while (!isOnSyncQueue(node)) {    //循环判断当前节点是否在同步队列中
 8         LockSupport.park(this);
 9         if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
10             break;    //检查节点在处于等待状态时是否被中断
11   }
12   //在跳出了循环,即被signal唤醒后重新加入了同步队列后,开始重新竞争锁
13   if (acquireQueued(node, savedState) && interruptMode != THROW_IE)    //acquireQueued自旋获取锁,具体分析见《2.从AbstractQueuedSynchronizer(AQS)说起(1)——独占模式的锁获取与释放》中对获取同步状态的解析
14     interruptMode = REINTERRUPT;
15   if (node.nextWaiter != null)
16       unlinkCancelledWaiters();    //如果节点从等待状态转换为在同步队列中,并且也已经获得了锁,此时将断开此节点后面的等待节点
17   if (interruptMode != 0)
18       reportInterruptAfterWait(interruptMode);
19  }

  在获取锁的线程调用await时,首先会将线程构造为Node节点并释放锁,此时线程被移出同步队列加入到Condition等待队列中,接着在第7行就会while循环判断节点是否在同步队列中,当没有线程调用signal方法的时候显然线程不在同步队列,并将一直循环,直到有线程调用signal方法该线程才会被唤醒加入到同步队列中,此时才会跳出循环。

  signal和signalAll方法的异同在和notify和notifyAll一样。signal只会唤醒等待队列中位于队首的节点使其具有竞争锁的资格,而signalAll则会唤醒等待队列中所有节点使所有节点都具有竞争锁的资格。

1 public final void signal() {
2     if (!isHeldExclusively())    //判断当前线程是否持有锁
3         throw new IllegalMonitorStateException();
4     Node first = firstWaiter;
5     if (first != null)
6         doSignal(first);        //唤醒等待队列中的第一个节点
7 }

  对比signalAll方法,不同点在于第6行是唤醒等待队列中的所有节点——doSignalAll(first),不再贴出代码。

private void doSignal(Node first) {
    do {
        if ((firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
} while (!transferForSignal(first) && (first = firstWaiter) != null)    //transferForSignal方法将处于等待队列中的节点添加到同步队列中
}

  至于doSignalAll则是循环调用transferForSignal使得所有节点都被唤醒加入到同步队列中。

  当节点从等待队列中加入到同步队列中时,呼应await中的循环等待节点是否在同步队列中,await和signal的协同配合也就很清晰明了了。

时间: 2024-12-26 02:58:34

6.类似Object监视器方法的Condition接口的相关文章

Condition接口

目录 Condition接口 一. Condition介绍及使用 二. Condition接口方法描述 Condition接口 一. Condition介绍及使用 Condition接口是为了与Lock配合实现等待/通知模式, 可以将Condition等待通知和Lock的关系 与 Object的等待通知和Synchronized的关系类比; Synchronized是通过锁对象即Object的wait() 和 notify() 实现等待通知; Lock 则可以通过Condition的await(

C# IComparable接口、IComparer接口和CompareTo(Object x)方法、Compare()方法

在项目中经常会用到字符串比较,但是有时候对字符串的操作比较多,规则各异.比如有的地方我们需要用排序规则,有的地方需要忽略大小写,我们该如何写一个比较容易操作的比较方法呢?重新实现IComparer接口不失为一个好办法. IComparable.CompareTo 方法 在MSDN上是这么解释(机器翻译过来)的: IComparable接口:定义一种特定于类型的通用比较方法,值类型或类通过实现此方法对其实例进行排序. IComparer接口:公开一种比较两个对象的方法. 详细理解就是: 在默认情况

Condition接口及其主要实现类ConditionObject源码浅析

任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包括wait().wait(long timeout).notify()以及notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式,这种实现主要体现在在虚拟机层面(对象头)和字节码(monitoreter monitorexit和synchronized方法修饰符)层面的支持 .Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现

Object.equals() 方法

Object.equals() 方法: 1 public class EqualsTest1 { 2 public static void main(String[] args) { 3 //Cat c1 = new Cat(); 4 //Cat c2 = new Cat(); 5 //System.out.println(c1 == c2);//result:false 6 Cat c3 = new Cat(1,2,3); 7 Cat c4 = new Cat(1,2,3);//在重写equa

ES6中Object.assign() 方法

1. 对象合并Object.assign 方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象上.如下代码演示: var target = {a: 0}; var source1 = {b: 1}; var source2 = {c: 2}; Object.assign(target, source1, source2); console.log(target); // 输出 {a: 0, b: 1, c: 2} 1-1 如果目标对象与源对象有同名属性,或多个源对象有同名

iOS 不定参数方法,类似UIAlertView的方法

//-------------------------- 方法申明 -------------------------- /** *  模仿UIAlertView方法 * *  @param argument 不定参数个数 */ - (void)copyAlertViewMethod:(id)argument, ... NS_REQUIRES_NIL_TERMINATION;//加了NS_REQUIRES_NIL_TERMINATION自动补齐功能就会自动在后面加nil //----------

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象。

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象. 语法EDIT Object.defineProperty(obj, prop, descriptor) 参数 obj 需要定义属性的对象. prop 需定义或修改的属性的名字. descriptor 将被定义或修改的属性的描述符. 返回值 返回传入函数的对象,即第一个参数obj 描述EDIT 该方法允许精确添加或修改对象的属性.一般情况下,我们为对象添加属性是通过

Object处理方法返回值

创建一个User实体类,并在其中写上属性值,并将其进行封装: public class User { private String name; private int age; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { t

Object处理方法的返回值

创建一个User实体类,并在其中写上属性值,并将其进行封装: public class User { private String name; private int age; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { t