调度程序可以挂起一个线程并执行另一个线程/工作吗?

时间:2017-03-01 12:27:06

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

让我们拥有以下代码(我们将在单核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

P.S。

此示例不强制使用单线程执行程序。强制要求我们只有一个CPU核心

3 个答案:

答案 0 :(得分:4)

对于任意线程,这在Java级别上无法完成,因为没有非deprecated methods暂停它。所有有效的控制方法都包括主动屈服的线程。

但是,操作系统本身通常有一个调度程序,可以定期挂起并恢复所有正在运行的进程,从而可以获得比可用CPU核心数更多的进程。此外,Java虚拟机通常不会像单个进程一样运行(green threads属于过去),每个线程有一个进程。

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

答案 1 :(得分:2)

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

如果它是单个线程执行器,答案是没有重叠,如果它不是单个线程执行器,则无法证明不会重叠。

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

  

可以通过先发生关系来排序两个动作。如果一个   行动发生在另一个之前,然后第一个是可见的和   在第二天之前订购。

     

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

     

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

     

在构造函数的结尾处有一个发生前的边缘   反对该对象的终结器(第12.6节)的开始。

     

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

     

如果是hb(x,y)和hb(y,z),那么hb(x,z)。

     

类Object(第17.2.1节)的等待方法具有锁定和解锁功能   与他们有关的行动;他们发生在以前的关系之前   由这些相关行动定义。

     

应该注意到之前发生的关系   两次行动之间并不一定意味着必须采取行动   在实现中按顺序放置。如果重新排序产生   结果与法律执行一致,这不违法。

在单线程执行程序的情况下,这正是我们得到的:两个runnables是同一个线程的动作,一个将按程序顺序排在另一个之前。虽然最后一段允许重新排序,但重新排序不会导致正确同步代码中的明显差异。

有多个线程,这是任何人的猜测。只有两件事是有保障的:

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

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

答案 2 :(得分:0)

[[@ biziclop的答案是正确的,虽然长而且令人困惑。 ]

  

从理论上讲,一个任务是否可以与另一个任务相结合,我们会看到这样的输出:

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

ExecutorService executorService = Executors.newSingleThreadExecutor();

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

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

  

此示例不强制使用单线程执行程序。强制要求我们只有一个CPU核心

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