Java happens-before relationship invokeAndWait

huangapple go评论73阅读模式
英文:

Java happens-before relationship invokeAndWait

问题

我的问题与问题相关,该问题已有答案:

> 是的,在调用 invokeLater/invokeAndWait 的线程的操作与在可运行对象的 EDT 上的操作之间存在一个 happens-before 关系。

我的问题更加一般:是否可能实现一个方法,例如 invokeAndWait,使其能够正常工作,但不会引入happens-before关系?通过正常工作,我指的是以下内容:

  • 保证执行提交的 Runnable 正好一次。
  • 执行提交的 Runnable 在特定线程上执行。
  • 方法在执行提交的 Runnable 完成之前等待执行。
  • 方法保证在提交的 Runnable 执行完成后返回。

在我看来,似乎没有办法在不引入happens-before关系的情况下实现这一点,我是否有误?如果是这样,请提供一个能够证明这一点的示例实现。

英文:

My question is related to this question, which already has an answer:

> yes, there is a happens-before relationship imposed between actions
> of the thread calling invokeLater/invokeAndWait and actions on the
> EDT of the runnable thereby submitted.

My question is a bit more general: Is it even possible to implement a method, such as invokeAndWait, such that it works properly, but not imposing a happens-before relationship? By the method working properly I mean the following:

  • The submitted Runnable is guaranteed to be executed exactly once.
  • The submitted Runnable is executed on a specific thread.
  • The method waits until the execution of the submitted Runnable has finished.
  • The method is guaranteed to return after the execution of the submitted Runnable has finished.

To me there seems to be no way to implement this without imposing a happens-before relationship, or am I wrong? If so, please include an example implementation, which proves this.

答案1

得分: 2

这里最困难的要求是:

所提交的Runnable保证仅会被执行一次。

使用非volatilePlain)字段将工作任务从提交者传递给执行者,并不会创建 happens-before 关系,但也不能保证执行者会在有限的时间内看到任务,甚至可能根本不会看到任务。编译器可能会优化掉对该字段的赋值,或者在运行时,执行者线程可能只会从其缓存中读取该值,而不是从主内存中读取。

因此,对于使用Java 8或更低版本的代码,我会说答案是:“否,不可能有这样的invokeAndWait方法”(除非可能使用本机代码)。

然而,Java 9添加了内存模式 Opaque。Doug Lea的页面“Using JDK 9 Memory Order Modes”详细描述了这个功能,他是JEP 193的作者(该功能添加了这个功能)。最重要的是,Opaque 模式比volatile要弱,但仍然提供以下保证:

  • **进展。**写入最终是可见的。
    [...]
    例如,在仅对某个变量x进行修改的构造中,其中一个线程以Opaque(或更强)模式写入,X.setOpaque(this, 1),在while(X.getOpaque(this)!=1){}中旋转的任何其他线程最终将终止。
    [...]
    请注意,这个保证在Plain模式下不成立,在这种模式下,自旋循环可能会(通常会)无限循环[...]。

在设计没有 happens-before 关系的invokeAndWait方法时,还必须考虑在启动线程之前的动作是线程中的第一个动作(JLS §17.4.4)。因此,在构造操作之前必须启动工作线程。

此外,还必须考虑“final字段语义”(JLS §17.15.1)。当invokeAndWait的调用者以lambda表达式的形式创建Runnable时,lambda对变量的捕获具有(据我理解)隐含的final字段语义。

如果可以的话,请提供一个示例实现,证明这一点。

使用示例来证明或否定线程安全性或 happens-before 关系是困难的,甚至是不可能的,因为它取决于硬件和时序。然而,像jcstress这样的工具可以帮助解决这个问题。

以下是一个(简化的)没有 happens-before 关系的invokeAndWait的潜在实现。请注意,我对Java内存模型并不完全熟悉,因此代码中可能存在错误。

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

class OpaqueExecutor {
  // 简化起见,假设只会有一个待处理任务
  // 因此,待处理任务被其他任务替换的情况不成问题
  private final AtomicReference<Runnable> nextTask = new AtomicReference<>();

  public OpaqueExecutor() {
    Thread worker = new Thread(() -> {
      while (true) {
        // 使用getOpaque()以不创建happens-before关系
        Runnable task = nextTask.getOpaque();
        if (task == null) {
          // 为了效率,通知JVM这是忙等待
          Thread.onSpinWait();
        } else {
          // 清除待处理任务;内存模式在这里无关紧要,因为我们只想保证这个线程不再看到任务
          nextTask.setPlain(null);
          task.run();
        }
      }
    }, "Worker thread");
    worker.setDaemon(true);
    worker.start();
  }

  public void invokeLater(Runnable runnable) {
    // 简化起见,假设没有现有的待处理任务会被替换
    // 使用setOpaque(...)以不创建happens-before关系
    nextTask.setOpaque(runnable);
  }

  private static class Task implements Runnable {
    private final AtomicBoolean isFinished = new AtomicBoolean(false);
    // 不能是final,以防止来自final字段语义的happens-before关系
    private Runnable runnable;

    public Task(Runnable runnable) {
      this.runnable = runnable;
    }

    public void run() {
      try {
        runnable.run();
      } finally {
        // 使用setOpaque(...)以不创建happens-before关系
        isFinished.setOpaque(true);
      }
    }

    public void join() {
      // 使用getOpaque()以不创建happens-before关系
      while (!isFinished.getOpaque()) {
        // 为了效率,通知JVM这是忙等待
        Thread.onSpinWait();
      }
    }
  }

  public void invokeAndWait(Runnable runnable) {
    Task task = new Task(runnable);
    invokeLater(task);
    task.join();
  }

  public static void main(String... args) {
    // 在创建执行者之前作为第一步,以不创建Thread.start()的happens-before关系
    OpaqueExecutor executor = new OpaqueExecutor();

    final int expectedValue = 123;
    final int expectedNewValue = 456;

    class MyTask implements Runnable {
      // 不能是final,以防止来自final字段语义的happens-before关系
      int value;

      public MyTask(int value) {
        this.value = value;
      }

      public void run() {
        int valueL = value;
        if (valueL == expectedValue) {
          System.out.println("Found expected value");
        } else {
          System.out.println("Unexpected value: " + valueL);
        }

        value = expectedNewValue;
      }
   

<details>
<summary>英文:</summary>

The most difficult requirement here is:
&gt; The submitted `Runnable` is guaranteed to be executed exactly once.

Using a non-`volatile` (_Plain_) field for transfering the work task from the submitter to the executor would not create a _happens-before_  relationship, but would also not guarantee that the executor sees the task at all or in a finite amount of time. The compiler would be able to optimize away assignments to that field, or during runtime the executor thread might only read the value from its cache instead of from main memory.

So for code using Java 8 or lower, I would say the answer is &quot;No, such an `invokeAndWait` method is not possible&quot; (except maybe using native code).

However, Java 9 added the memory mode _Opaque_. The page &quot;[Using JDK 9 Memory Order Modes](http://gee.cs.oswego.edu/dl/html/j9mm.html)&quot; by Doug Lea, the author of [JEP 193](https://openjdk.java.net/jeps/193) (which added this functionality), describes this in great detail. Most importantly _Opaque_ mode is weaker than `volatile` but provides still the following guarantee:
&gt; - **Progress.** Writes are eventually visible.  
&gt; [...]  
&gt; For example in constructions in which the only modification of some variable x is for one thread to write in Opaque (or stronger) mode, `X.setOpaque(this, 1)`, any other thread spinning in `while(X.getOpaque(this)!=1){}` will eventually terminate.  
&gt; [...]  
&gt; Note that this guarantee does NOT hold in Plain mode, in which spin loops may (and usually do) infinitely loop [...]

When designing such an `invokeAndWait` method without _happens-before_ relationship you also have to consider that an action before starting a thread _happens-before_ the first action in that thread ([JLS &#167;17.4.4](https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.4.4)). So the worker thread must be started before the action is constructed.

Additionally the &quot;`final` field semantics&quot; ([JLS &#167;17.15.1](https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.5.1)) have to be considered. When the caller of `invokeAndWait` creates the `Runnable` in the form of a lambda expression, then the capturing of variables by that lambda has (to my understanding) implicit `final` field semantics.

&gt; If so, please include an example implementation, which proves this.

Proving or disproving thread-safety or _happens-before_ relationships using examples is difficult, if not impossible, due to being hardware and timing dependent. However, tools like [jcstress](https://openjdk.java.net/projects/code-tools/jcstress/) can help with this.

Below is a (simplified) potential implementation for an `invokeAndWait` without _happens-before_ relationship. Note that I am not completely familiar with the Java Memory Model so there might be errors in the code.
```java
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

class OpaqueExecutor {
  // For simplicity assume there will every only be a single work task
  // So pending task being replaced by other task is not an issue
  private final AtomicReference&lt;Runnable&gt; nextTask = new AtomicReference&lt;&gt;();

  public OpaqueExecutor() {
    Thread worker = new Thread(() -&gt; {
      while (true) {
        // Use getOpaque() to no create happens-before relationship
        Runnable task = nextTask.getOpaque();
        if (task == null) {
          // For efficiency indicate to the JVM that this is busy-waiting
          Thread.onSpinWait();
        } else {
          // Clear pending task; memory mode here does not matter because we only want
          // to guarantee that this thread does not see task again
          nextTask.setPlain(null);
          task.run();
        }
      }
    }, &quot;Worker thread&quot;);
    worker.setDaemon(true);
    worker.start();
  }

  public void invokeLater(Runnable runnable) {
    // For simplicity assume that there is no existing pending task which could be
    // replaced by this
    // Use setOpaque(...) to not create happens-before relationship
    nextTask.setOpaque(runnable);
  }

  private static class Task implements Runnable {
    private final AtomicBoolean isFinished = new AtomicBoolean(false);
    // Must NOT be final to prevent happens-before relationship from
    // final field semantics
    private Runnable runnable;

    public Task(Runnable runnable) {
      this.runnable = runnable;
    }

    public void run() {
      try {
        runnable.run();
      } finally {
        // Use setOpaque(...) to not create happens-before relationship
        isFinished.setOpaque(true);
      }
    }

    public void join() {
      // Use getOpaque() to no create happens-before relationship
      while (!isFinished.getOpaque()) {
        // For efficiency indicate to the JVM that this is busy-waiting
        Thread.onSpinWait();
      }
    }
  }

  public void invokeAndWait(Runnable runnable) {
    Task task = new Task(runnable);
    invokeLater(task);
    task.join();
  }

  public static void main(String... args) {
    // Create executor as first step to not create happens-before relationship
    // for Thread.start()
    OpaqueExecutor executor = new OpaqueExecutor();

    final int expectedValue = 123;
    final int expectedNewValue = 456;

    class MyTask implements Runnable {
      // Must NOT be final to prevent happens-before relationship from
      // final field semantics
      int value;

      public MyTask(int value) {
        this.value = value;
      }

      public void run() {
        int valueL = value;
        if (valueL == expectedValue) {
          System.out.println(&quot;Found expected value&quot;);
        } else {
          System.out.println(&quot;Unexpected value: &quot; + valueL);
        }

        value = expectedNewValue;
      }
    }

    MyTask task = new MyTask(expectedValue);
    executor.invokeAndWait(task);

    int newValue = task.value;
    if (newValue == expectedNewValue) {
      System.out.println(&quot;Found expected new value&quot;);
    } else {
      System.out.println(&quot;Unexpected new value: &quot; + newValue);
    }
  }
}

huangapple
  • 本文由 发表于 2020年10月23日 17:26:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/64497416.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定