Java 8 中并发环境下是否存在活性检测失败的问题?

3
我会尝试复制《Effective Java(第二版)》中提到的以下代码,它属于第10章并发性条目66。
public class StopThread {
   private static boolean stopRequested;

   public static void main(String args[]) {
      Thread backgroudThread = new Thread(new Runnable(){
             public void run() {
                int i = 0;
                while(!stopRequested) {
                       i ++;
                }
             }
      });
      backgroudThread.start();

      TimeUnit.SECONDS.sleep(1);
      stopRequested = true;
   }
}

根据作者的描述,当您运行以上过程段时,背景线程不会终止。但是在我设置了Java8环境的机器上无法复现此问题。那么这个问题是否已经在Java最新版本中得到优化呢?
更新: 我在Linux操作系统(Redhat)中再次尝试。它总是不终止。在我之前在Win7操作系统COREi5中运行它时,它总是终止。
PS: 在Linux操作系统(Redhat)中,如果按照上述代码和程序运行,程序将不会终止。但是,如果我在“while”循环中的“i ++”之后添加“System.out.println(i)”,此时程序将始终终止。这是我的新发现。如果您知道原因,请发布您的答案。我将继续钻研这些系列问题,直到找到真正的原因。

1
生活中的一个悲哀事实是:多线程问题没有产生可见效果并不意味着它不会成为一个问题。这就是编写多线程代码如此痛苦的原因之一。 - user2357112
6
作者实际上是说它不会被终止还是可能不会被终止?这里肯定没有保证它会被终止。 - Jon Skeet
@Jon,Effective Java 的作者是Josh Bloch,所以我认为我们可以假设后者。 - Voo
@Voo:我也怀疑是后者,但我现在手头没有EJ的副本。 - Jon Skeet
是的,请参考alfasin的答案。它引用了属于EJ的句子。作者写道:“然而,在我的机器上,程序从未终止:后台线程永远循环!” - Ivan_Wang
4个回答

3

更准确地说,作者写道:

然而,在我的机器上,程序永远不会终止:后台线程无限循环!

现在你可能还可以对此进行争论,因为他显然没有将这个程序运行“永远”,但你明白了。

顺便说一下,在我的机器上,程序也永远不会终止(JDK 1.7.0_45)
顺便提一下,如果我们想让它终止,我们只需要声明stopRequestedvolatile

更新:
为了为那些没有这本书的人提供更多背景信息(你真的应该买一本!),作者后来解释说,这种情况可能发生是由于编译器检测到stopRequested没有在本地更改,因此允许以下优化:

while(!stopRequested) {
      i ++;
}

变成:

if (!stopRequested)
    while(true) {
          i ++;
    }

JVM hotspot进行这种优化(称为hoisting)。如果您想深入了解为什么它是一个合法的优化,您应该阅读一些内容:JLS §17.4.3 Programs and Program Order


是的,你从作者那里引用得非常准确。我很困惑为什么你的机器可以像作者描述的那样运行这个问题。但我却不能。 - Ivan_Wang
@Ivan_Wang,计算机体系结构一直在不断发展。Java 语言规范有意留下了一些多线程方面的未指定事项,以便 JVM 实现者可以自由地利用不同的体系结构和操作系统。如果每个 JVM 都必须完全像其他 JVM 一样运行,那么大多数 JVM 将变得效率低下。因此,请注意!如果规范表示某件事“可能”发生,则即使您无法证明它在任何给定的计算机上确实发生,也要相应地编写代码。 - Solomon Slow
@alfasin 谢谢你发布的更新内容。我不知道当前的Java版本中是否也存在提升优化。因为根据我对这种优化的理解,后台线程在没有同步的情况下永远不会停止。可能是我误解了。请帮助我指出我错在哪里。谢谢。 - Ivan_Wang
@Ivan_Wang 优化是编译器可以执行的,而不是强制性的。我相信Java 8也实现了提升,但可能不是在这个特定的情况下。是的,你的理解是正确的,使用这种优化意味着backgroudThread永远不会停止。 - Nir Alfasi
值得一提的是,我的JVM 8上负载可靠地被升起。 - Marko Topolnik
只是顺便提一下,生成的优化代码甚至不需要在循环内修改 i。所以更像是 if(!stopRequested) while(true); 但由于不能保证在启动线程后该代码的执行在一秒钟内开始,即使进行了这种优化,该线程在某些运行中仍可能终止。 - Holger

2
不,作者说在他的机器上它永远无法终止。这是完全可能的,也正是他试图说明的观点:对可变数据的共享访问应该同步,以确保原子性和可见性
虽然将boolean分配为原子操作,但后台线程可能不会看到主线程对stopRequested的更改。
作者还提出了一个简单的解决方法:将stopRequested声明为volatile

1
根据作者的描述,当您运行上述过程段时,后台线程将不会终止。
我没有书在手边,但我强烈怀疑它并没有这么说。
我认为它实际上是说类似于后台线程可能不会被终止。 "will not"和"might not"这两个词意思不同...
更新-请参阅@alfasin的答案,了解Bloch实际写下的内容。

但是我在我的机器上无法重现它,该机器设置了Java8环境。那么这个问题在Java最新版本中是否已经得到优化?

不是"优化",而是不同的处理方式。

实际上正在发生的事情是程序已经进入了Java语言规范中规定行为未指定的区域。特别地,不能保证后台线程会看到主线程对标志所做的更改。但是它仍然可能看到...这取决于各种各样的因素。

你无法在Java 8上重现这个问题并不奇怪。你也可能在旧的Java平台上难以重现它。从某种意义上说,正是这种"困难",使得这种问题如此棘手。


1
这个问题是否已经在Java的最新版本中得到了优化?
事实恰好相反:这个“问题”正是由于Java内存模型允许的优化而存在。这些规定绝对没有从Java 8规范中删除,它们将会一直存在。它们在多线程性能方面提供了重大的优势。
Java提供了特定的语言特性,使您的示例能够正常工作,但它并不强制要求在每个地方都使用这些特性; 它将其留给程序员自行决定,在需要时才应用它们以及它们的性能成本。
顺便说一下,我运行了您问题中的确切代码(加上缺少的“throws”声明),并且该程序可靠地在我的OpenJDK 1.8上未能终止。

好的,谢谢。就像你说的一样,你运行了我的代码,程序肯定没有终止。但是我运行它总是会终止。如果你多次运行它,它不稳定地终止吗?还是偶尔不终止?谢谢。 - Ivan_Wang
这就是我所说的,它在我的机器上总是无法终止。但由于JMM声明你那边的行为与我的一样合法,这并不奇怪。 - Marko Topolnik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接