设为首页 加入收藏

TOP

条件队列大法好 :wait 和 notify 的基本语义(一)
2017-11-27 13:06:51 】 浏览:431
Tags:条件 队列 大法 wait notify 基本 语义

条件队列是我们常用的轻量级同步机制,也被称为“wait+notify”机制。但很多刚刚接触并发的朋友可能会对wait和notify的语义和配合过程感到迷惑。

今天从join()方法的实现切入,重点讲解wait()方法的语义,简略提及notify()与notifyAll()的语义,最后总结二者的配合过程。

本篇的知识点很浅,但牢固掌握很重要。后面会再写一篇文章,介绍wait+nofity的用法,和使用时的一些问题。

基本概念

线程、Thread与Object

在理解“wait+notify”机制时,注意区分线程、Thread与Object的概念,明确三者在wait、 notify、锁竞争等事件中充当的角色:

  • 线程指操作系统中的线程
  • Thread指Java中的线程类
  • Object指Java中的对象

Thread继承自Object,也是一个对象(多态),并从Object类中继承得到了wait()、notify()(还有notifyAll())方法;同时,Thread也被JVM用于映射操作系统中的线程。

wait()

迷惑的join()方法

通过join()方法确认你是否理解了wait+notify机制:

Thread f = new Thread(new Runnable() {
  @Overide
  public run() {
    Thread s = new Thread(new Runnable() {
      @Overide
      public run() {
        for (int i : 1000000) {
          sout(i);
        }
      }
    });
    s.start();
    sout("************* son thread started *************");
    s.join();
    sout("************* son thread died *************");
  }
});
f.start();

join()方法的语义很简单,可以不严谨的表述为“让父线程等待子线程退出”。现在我们来观察Thread#join()的实现,让你对这个语义产生迷惑:

public final synchronized void join(long millis)
throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

重点看15-22行。逻辑很简单,一个限时阻塞的经典写法。不过,你可能会产生和我一样的迷惑:

为什么调用子线程的wait()方法,进入等待状态的却是父线程呢?

分析

让我们用前面提到的线程、Thread和Object三个概念来解释这段代码。事件序列如下:

  • 主线程t0执行1-17行,在Java中创建了Thread实例f,处于NEW状态;同时,f也是一个Object实例
  • 主线程t0执行18行后,操作系统中创建了线程t1,Thread实例f转入RUNNABLE状态(Java中,Thread没有RUN状态,因为线程是否正在执行由JVM之外的调度策略决定)
  • 假设线程t1正在执行,则线程t1执行4-11行,在Java中创建了Thread实例s,处于NEW状态;同时,s也是一个Object实例
  • 线程t1执行12行后,操作系统中创建了线程t2,Thread实例s转入RUNNABLE状态
  • 假设线程t1、t2均正在执行,则线程t1执行12行之后、14行之前,可能线程t1与线程t2同时在向标准输出打印内容(t1执行13行,t2执行7-9行)
  • 线程t1执行14行的过程中,操作系统中的线程t1转入阻塞或等待状态(取决于操作系统的实现),Thread实例f转入TIMED_WAITING状态,Thread实例s不受影响,仍处于RUNNABLE状态
  • 线程t2死亡后,被操作系统标记为死亡,Thread实例s转入为TERMINATED状态
  • 线程t1中,Thread实例f发现Thread实例s不再存活,随即转入RUNNABLE状态,操作系统中的线程t1转入运行状态
  • 线程t1从14行s.join()返回,执行15行,打印
  • 最后,线程t1死亡,Thread实例也转入了TERMINATED状态

当然,在事件6(线程t1执行14行的过程中),Thread实例f在TIMED_WAITING状态与RUNNABLE状态之间来回转换,也因此,才能发现Thread实例s不再存活。但可忽略RUNNABLE状态,不影响理解。
上一节提出的问题忽略了线程、Thread与Object的区别。现在,耐心分析过事件序列之后,让我们使用这三个概念,重新表述该问题:

为什么在父线程t1中调用s.join(),进而调用s.wait(),进入等待状态的却是Thread实例f对应的父线程t1,而不是子线程t2呢?

该表述同时也是回答。因为wait()影响的是调用wait()的线程,而不是wait()所属的Object实例。具体说,wait()的语义是“将调用s.wait()的线程t1放入Object实例s的等待集合”。这与s是否同时是Thread实例并无关系——如果s恰好是一个Thread实例,那么其所对应的线程t2可以照常运行,毫无影响。

虽然线程的状态与Thread实例的状态不能一一对应,但用Thread实例的状态代替线程的状态,可以简化条件队列的模型,又不影响核心的正确性。在事件6(线程t1执行14行的过程中)中,各角色的关系如图:

更容易理解的用法

我们之所以会在join()方法的实现上产生困惑,是因为它以一种难以理解的姿势使用wait+notify机制。
wait+notify机制本质上是一种基于条件队列的同步。JVM为每个对象都内置了监视器,与java.util.concurrent包中的条件队列Condition对应。

条件队列本身很容易理解,但join()方法使用wait()的姿势让人迷惑。它将Thread实例s作为条件队列,共享于父线程t1、子线程t2中——Thread实例s既能够被创建它的Thread实例f访问,也能够被它自己(this)访问。可读性很差,不建议学习。

那么,如何使用wait()才更容易理解呢?可参考Java实现生产者-消费者模型中的“实现二:wait && notify”,使用明确可读的条件队列。简化如下:

public class WaitNotifyModel implements Model {
  private final
首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇聊一聊 Spring 中的线程安全性 下一篇成小胖学习 ActiveMQ · 基础篇

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目