当前位置: 首页 > 知识库问答 >
问题:

线具有非易失性布尔变量的睡眠行为

夏侯兴怀
2023-03-14

根据JLS 17规范第17.3节:

例如,在下面的(断开的)代码片段中,假设这是。“完成”是一个非易失性布尔字段:

while (!this.done)
    Thread.sleep(1000);

编译器可以自由读取此字段。只执行一次,并在每次执行循环时重用缓存的值。这意味着循环永远不会终止,即使另一个线程更改了这个值。完成

我尝试模拟以下示例:两个线程同时访问同一个布尔变量,第一个线程使用while循环中的共享布尔值,第二个线程更新布尔值。

1、代码无线程。第一个线程内的sleep():

public boolean done;
public void performTest() throws InterruptedException {
    done = false;
    new Thread(() -> {
        System.out.println("Running Thread 1...");
        int count = 0;
        while (!done) {
            count++;
        }
        System.out.println("Exiting thread...");
    }).start();
    Thread.sleep(100);
    new Thread(() -> {
        System.out.println("Thread 2 setting done to true");
        done = true;
    }).start();
}

-

2.现在更改代码以在JLS中提到的同时循环中包含Thread.sleep()

public boolean done;

public void performTest() throws InterruptedException {
    done = false;
    new Thread(() -> {
        System.out.println("Running Thread 1...");
        int count = 0;
        while (!done) {
            count++;
            try {
                Thread.sleep(0);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Exiting thread...");
    }).start();
    Thread.sleep(100);
    new Thread(() -> {
        System.out.println("Thread 2 setting done to true");
        done = true;
    }).start();
}

-

所以我在这个例子和提到的JLS之间感到困惑。不确定我在这里错过了什么。

注意:我还注意到Venkat在他的一个视频中也提到了这个例子,并且有一篇博客文章解释了这个行为,看起来有一些东西与JIT优化有关。这里真正让我担心的是,这个示例与JLS中描述的不同。

共有1个答案

林德华
2023-03-14

在您的示例中,代码被破坏的原因是JVM可以自由地使用缓存版本的done,这样循环就永远不会结束。当你有线的时候。sleep()'在那里,这不太可能发生,但仍然是可能的。这意味着,您编写了一些代码并对其进行了测试,结果非常好。然后,您更改了一个环境,或者更改了一个JVM,突然它就坏了。

这是一个糟糕的基准,但它给出了一个想法。

public class VolatileTest implements Runnable{
  boolean done = false;
  public void run(){
    long count = 0;
    long start = System.nanoTime();
    long end = Integer.MAX_VALUE;
    while(!done){
        count++;   
        if(count == end){
            break;
        }     
        //try{ Thread.sleep(0); } catch (Exception e){ break;}
    }
    System.out.println( System.nanoTime() - start + " with " + count + " iterations");
  }
  public static void main(String[] args) throws Exception{
  
      VolatileTest vt = new VolatileTest();
      new Thread(vt).start();
      Thread.sleep(500);
      vt.done = true;
  }
}

现在有3个案例。第一个写的没有任何睡眠/挥发性。

65053733,2147483647次迭代

完成整数需要650ms。最大值迭代次数。注意,有时这比我等待的500毫秒要快。

第二种情况,挥发性<代码>完成<代码>。

499923823与1091070867迭代

现在,在vt.done设置为true之前,它永远不会完成。

第三种情况。非易失性线程。睡觉

499905166与3031374迭代

volatile版本的速度是线程的300倍。睡眠版本。非易失性版本在速度上更具间歇性,但它是最快的。我怀疑是因为当JIT决定缓存完成时,可以说它得到了速度提升。

我不知道如何验证它何时决定缓存done变量,但我想这就是为什么JMH对于这些类型的微基准测试是必要的。

 类似资料:
  • 我使用 C 和 POSIX 线程创建了一个多线程应用程序。我现在应该阻塞一个线程(主线程),直到设置了布尔标志(变为真)。 我找到了两种方法来完成这件事。 > 在没有睡眠的情况下旋转。 在睡眠中旋转循环。 如果我应该遵循第一种方式,为什么有些人编写代码遵循第二种方式?如果应该使用第二种方法,为什么要让当前线程Hibernate呢?这种方式的缺点是什么?

  • 谢谢,伊利亚

  • 我正在对科特林进行修补,我正试图让我的头脑了解科特林中可为空的变量是如何工作的。这里我有一段代码,它执行布尔检查,以查看车辆是否超载。实现是处理可空变量的好方法还是有更优雅的方法? 非常谢谢!

  • 我正在研究计算机硬件,在那里我们了解到使用硬件计时器比软件延迟获得更准确的结果。我已经在汇编中编写了1毫秒的软件延迟,我可以启动一个进程,使用这个延迟每毫秒重复一次,并使用计数器每100毫秒做一些其他事情,这种技术不如使用我现在要使用的硬件内置的硬件计时器准确。 所以我想知道Java内置的计时有多精确?我们有< code > system . current time millis 和< code

  • 我在Eclipse中有一些具有以下结构的代码: 我的期望是,在第一个代码运行后,等待10秒,然后等待第二个代码和其他10秒。但实际上,编译器在不等待我设置的10秒的情况下一个接一个地运行命令。 是否有任何强制条件使其工作? 非常感谢。

  • 在阅读了这个问题和这个(尤其是第二个答案)之后,我对volatile及其关于记忆障碍的语义感到非常困惑。 在上面的例子中,我们写入一个易失性变量,这会导致一个mitch,这反过来会将所有挂起的存储缓冲区/加载缓冲区刷新到主缓存,使其他缓存行无效。 然而,非易失性字段可以优化并存储在寄存器中,例如?那么,我们如何才能确保给定一个写入易失性变量之前的所有状态变化都是可见的呢?如果我们有1000件东西呢