【问题标题】:Can scheduler to suspend one thread and execute another thread/work?调度程序可以挂起一个线程并执行另一个线程/工作吗?
【发布时间】:2017-03-01 12:27:05
【问题描述】:

让我们有以下代码(我们将在单核 CPU 上运行它):

Runnable runnable1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("runnable_1_1");
        System.out.println("runnable_1_2");
    }
};
Runnable runnable2 = new Runnable() {
    @Override
    public void run() {
        System.out.println("runnable_2_1");
        System.out.println("runnable_2_2");
    }
};
ExecutorService executorService = Executors.newSingleThreadExecutor(); // or Executors.newCachedThreadExecutor();
executorService.execute(runnable1);
executorService.execute(runnable2);
executorService.shutdown();

理论上是否有可能将一项任务楔入另一项任务,我们会看到如下输出:

runnable_1_1
runnable_2_1
runnable_2_2
runnable_1_2

附言

单线程执行器对于本示例不是必需的。强制要求我们只有一个 CPU 内核

【问题讨论】:

    标签: java multithreading swing concurrency java-memory-model


    【解决方案1】:

    这不能在 Java 级别为任意线程干净地完成,因为没有非deprecated methods 可以挂起它。所有有效的控制方法都包括主动让步的线程。

    但是,操作系统本身通常有一个调度程序,它会定期挂起和恢复所有正在运行的进程,从而允许拥有比可用 CPU 内核数量多得多的进程。另外,Java虚拟机一般不会单进程运行(green threads属于过去),每个线程只有一个进程。

    因此,操作系统可能会暂停一个线程一小段时间,从而允许运行另一个线程或 Java 虚拟机之外的某个其他进程。一般的答案可能是肯定的。

    【讨论】:

    • @bashnesnos 为什么?
    • @gstackoverflow 我让其他人先回答
    • 因为我们需要 >1 个线程来“暂停一个线程一小段时间,让另一个线程运行”。
    • 单线程也可能在某个时候被挂起,因为Java虚拟机之外还有很多其他系统进程。
    【解决方案2】:

    当您尝试推理此级别的代码时,CPU 的数量并不重要。理论上,您可以在操作系统上运行 JVM,该操作系统会在每条程序指令之后强制进行上下文切换。这会很疯狂,没有操作系统能做到这一点,但你怎么能只看 Java 代码就知道呢?

    如果是单线程执行器,答案是不会有重叠,如果不是单线程执行器,你不能证明不会有重叠。

    要找到原因,我们需要看一下JLS的第17章:

    两个动作可以通过happens-before关系排序。如果一个 动作发生在另一个之前,然后第一个对和可见 在第二个之前订购。

    如果我们有两个动作 x 和 y,我们写 hb(x, y) 来表示 x 发生在 y 之前。

    如果 x 和 y 是同一个线程的动作并且 x 在 y 之前 程序顺序,然后是 hb(x, y)。

    从一个构造函数的末尾有一个happens-before边 对象指向该对象的终结器(第 12.6 节)的开头。

    如果动作 x 与后续动作 y 同步,那么我们也 有 hb(x, y)。

    如果 hb(x, y) 和 hb(y, z),则 hb(x, z)。

    Object 类(第 17.2.1 节)的等待方法有 lock 和 unlock 与他们相关的行动;他们的发生之前的关系是 由这些关联的操作定义。

    需要注意的是happens-before关系的存在 两个动作之间并不一定意味着他们必须采取 在实现中按该顺序放置。如果重新排序产生 结果符合合法执行,不违法。

    在单线程执行程序的情况下,这正是我们得到的:两个可运行对象是同一个线程的操作,一个将按程序顺序领先于另一个。尽管最后一段允许重新排序,但重新排序不会导致正确同步的代码出现明显差异。

    对于多个线程,这是任何人的猜测。只有两件事可以保证:

    1. 线程中的第一条消息将在第二条之前打印。 (见上文)
    2. 输出的每一行都将包含来自其中一个线程的完整消息,即您永远不会看到一行混乱的输出。这仅仅是因为PrintStream.println() 是同步的。

    这就是理论。

    在实践中,对于大多数 JVM 实现和操作系统,您可能永远不会看到与此确切代码的重叠,原因很简单,即您执行的任务花费的时间太短而不会被中断。让它们运行几分钟或几小时,你肯定会看到两者之间的上下文切换。

    【讨论】:

      【解决方案3】:

      [[ @biziclop 的答案是正确的,尽管冗长且令人困惑。 ]]

      理论上是否有可能将一项任务楔入另一项任务,我们将看到如下输出:

      不在您发布的代码中。您正在向单线程执行器提交 2 个作业:

      ExecutorService executorService = Executors.newSingleThreadExecutor();
      

      这意味着只有 1 个线程将执行您的 2 个Runnables。当第一个线程阻塞时,另一个 Runnable 没有执行,因此输出不会交错。第一个Runnable 需要在第二个Runnable 执行之前完成。

      如果您使用Executors.newCachedThreadExecutor();,那么2 个Runnables 可以同时运行并且它们的输出可以交错。在这种情况下,第一个Runnable 可能会打印出runnable_1_1,然后进行时间切片,以便另一个线程可以执行并显示它的runnable_2_1,等等。然而,这是一个竞争条件,可能不太可能,但它是可能。

      单线程执行器对于本示例不是必需的。强制要求我们只有一个 CPU 内核

      正如@biziclop 所提到的,硬件上的 CPU 数量并不重要。重要的是在任一时间点运行队列中有多少线程。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-05-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多