【问题标题】:Java happens-before relationship invokeAndWaitJava 发生之前的关系 invokeAndWait
【发布时间】:2021-02-06 08:47:09
【问题描述】:

我的问题与this问题有关,已经有了答案:

是的,动作之间存在 happens-before 关系 调用invokeLater/invokeAndWait 的线程和对 从而提交的可运行文件的 EDT。

我的问题有点笼统:是否有可能实现一个方法,例如invokeAndWait,使其正常工作,但不强加发生之前 关系?通过方法正常工作我的意思是:

  • 提交的Runnable保证只执行一次。
  • 提交的Runnable在特定线程上执行。
  • 该方法一直等待,直到提交的Runnable 的执行完成。
  • 该方法保证在提交的Runnable执行完成后返回。

对我来说,如果不施加 happens-before 关系,似乎没有办法实现这一点,还是我错了?如果是这样,请提供一个示例实现,它可以证明这一点。

【问题讨论】:

  • 出于好奇,为什么要在特定线程中完成计算,但同​​时又阻塞了调用线程?
  • 与问题本身无关,但您可能希望仅在特定线程上执行不是线程安全的代码,然后在另一个线程上等待结果。我认为这也是 SwingUtilities.invokeAndWait(java.lang.Runnable) 函数的用例。
  • 好的,这是为了防止该计算的并行性..

标签: java multithreading runnable happens-before invokeandwait


【解决方案1】:

这里最难的要求是:

提交的Runnable保证只执行一次。

使用非volatile (Plain) 字段将工作任务从提交者转移到执行者不会创建 happens-before 关系,但会也不保证执行者完全或在有限的时间内看到任务。编译器将能够优化对该字段的分配,或者在运行时执行线程可能只从其缓存而不是主内存中读取值。

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

不过,Java 9 添加了内存模式不透明JEP 193(添加了此功能)的作者 Doug Lea 的页面“Using JDK 9 Memory Order Modes”对此进行了非常详细的描述。最重要的是不透明模式比volatile弱,但仍然提供以下保证:

  • 进展。写入最终是可见的。
    [...]
    例如,在某个变量 x 的唯一修改是让一个线程以不透明(或更强)模式写入 X.setOpaque(this, 1) 的构造中,任何其他在 while(X.getOpaque(this)!=1){} 中旋转的线程最终将终止。
    [...]
    请注意,此保证不适用于普通模式,其中自旋循环可能(并且通常会)无限循环 [...]

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

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

如果是这样,请提供一个示例实现,以证明这一点。

使用示例证明或反驳线程安全或 happens-before 关系是困难的,如果不是不可能的话,因为它依赖于硬件和时间。但是,jcstress 之类的工具可以帮助解决这个问题。

下面是invokeAndWait 的(简化的)潜在实现,没有happens-before 关系。请注意,我对 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<Runnable> nextTask = new AtomicReference<>();

  public OpaqueExecutor() {
    Thread worker = new Thread(() -> {
      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();
        }
      }
    }, "Worker thread");
    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("Found expected value");
        } else {
          System.out.println("Unexpected value: " + valueL);
        }

        value = expectedNewValue;
      }
    }

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-04-21
    • 2017-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-02
    相关资源
    最近更新 更多