在 system.exit() 之后线程仍然 运行

threads still running after system.exit()

我正在 Java 中学习多线程,并且正在试验线程。我想出了一种竞争条件代码(有点......请原谅这个菜鸟),其中 2 个线程循环工作(100 次)并竞相更改静态变量。目标是当一个线程第一次完成循环时(在时间片环境中或并行地 运行)打印变量的值并调用 system.exit(int status) 来终止程序!这是为了找出哪个线程赢得了比赛!

预期输出应为:nameOfTheThreadThatWonTheRace + "--" + valueOf'I'IncrementedInTheLoop

代码如下:

public class Nest
{
  public static void main(String[] args) throws Exception
  {
    Thread r1 = new Thread(new Racer(),"R1");
    Thread r2 = new Thread(new Racer(),"R2");
    r1.start();
    r2.start();
  }
}

class Racer implements Runnable
{
  public void run()
  {
    for(int i = 0; i <= 100; i++)
    {
      Value.value = Thread.currentThread().getName() + "--" + i;
    }
    Value.printValue();
    System.exit(0);
  }
}

class Value
{
  static String value = null;

  public static void printValue()
  {
    System.out.println(value);
  }
}

但是实际输出不同(4 次运行):

  1. R2--100 R2--100

  2. R2--84 R2--100

  3. R1--100 R1--100

  4. R2--39 R2--100

我很困惑为什么 JVM 在任一线程到达 'System.exit(0)' 行后不停止? exit() 是否仅关闭主线程或整个 JVM,或者仍在执行的线程正在阻止 JVM 暂停?

另外请解释一下为什么要生成2行o/p?

For extra info:
Processor--> Intel® Core™ i5-7200U CPU @ 2.50GHz × 4 
Ram--> 8GB
OS--> Fed27 workstation

(我还没有接触过 java.util.concurrent API 'yet' 但我知道我可以在那里以不同的方式管理线程..)

如果您能用正常的线程术语而不是指并发 API 来解释,我将不胜感激。

感谢您的帮助,再次原谅这个菜鸟:)

处理 System.exit(0) 需要时间,因此当调用它的线程正在等待操作系统终止并回收 运行 在不同内核上运行的代码时,就是运行ning在不同的内核上都能完成。这使它看起来像两个线程 "won the race".

请记住,在多线程环境中,一个线程无法显式控制另一个线程的时间;即使在系统关闭时。如果你想要那种控制,你需要明确地将它写入程序(例如通过让两个线程检查线程安全标志,以确定是否仍在竞争运行)。

调用 System.exit() 将关闭 JVM(请参阅 javadocs or the language specs)。

两个线程正在并行更新 Value 的静态成员。此类不安全更新的结果未定义。这就是您看到 strage 输出的原因。

一种解决方案是在 Racer class 中使用局部变量或字段,因为您不必在两个线程之间共享数据。此外,您甚至不需要它,因为您可以使用 Thread.currentThread().getName()

获取当前线程的名称

我想如果你在你的程序中添加一点日志记录,那么它可以帮助你理解到底发生了什么:

    public void run()
      {
        for(int i = 0; i <= 100; i++)
        {
        if(i==100)
        {
            System.out.println(Thread.currentThread().getName() +" : before setting value 100th time : " + System.currentTimeMillis());
        }
          Value.value = Thread.currentThread().getName() + "--" + i;
          if(i==100)
        {
            System.out.println(Thread.currentThread().getName() +" : after setting value 100th time : " + System.currentTimeMillis());
        }
        }
        System.out.println(Thread.currentThread().getName() +" : before printing : " + System.currentTimeMillis());
        Value.printValue();
        System.out.println(Thread.currentThread().getName() +" : before exiting : " + System.currentTimeMillis());
        System.exit(0);
      }


class Value
{
  static String value = null;

  public static void printValue()
  {
    System.out.println(Thread.currentThread().getName() + " : value : " + value + " : " + System.currentTimeMillis());
  }
}

首先,如果任何线程调用 System.exit(),那么 JVM 将终止(杀死所有线程!!!)。它不会等待任何线程执行,但会调用关闭挂钩、未调用的终结器等。因此,JVM 可能需要一些时间才能完成。您可以在这里阅读更多内容:

https://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html#exit(int)

现在,您只提到了 4 运行 的输出。如果你再 运行 几次,我相信你会得到以下输出:

R2--100

这显然意味着 JVM 在另一个线程可以执行之前终止。

现在对于你的输出,说

R2--100 R2--100

两个线程都在 JVM 终止之前执行,很简单!

需要注意的一件事是两个线程的名称都是 R2。这是因为值变量是静态的并且在两个线程之间共享。由于竞争条件,在 R2 可以打印该值之前,它已由 R1 设置。如果您添加我建议的日志记录,您可以看到这一点。

参见下面的示例输出:

R2 : before setting value 100th time : 1517579707689
R2 : after setting value 100th time : 1517579707690
R1 : before setting value 100th time : 1517579707689
R1 : after setting value 100th time : 1517579707691
R2 : before printing : 1517579707691
R1 : before printing : 1517579707692
R2 : value : R1--100
R1 : value : R1--100
R2 : before exiting : 1517579707694
R1 : before exiting : 1517579707694