Java 发生在关系 invokeAndWait 之前 [英] Java happens-before relationship invokeAndWait

查看:67
本文介绍了Java 发生在关系 invokeAndWait 之前的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

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.

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

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:

  • 提交的Runnable保证只执行一次.
  • 提交的 Runnable 在特定线程上执行.
  • 该方法一直等到提交的 Runnable 执行完成.
  • 该方法保证在提交的Runnable 执行完成后返回.
  • 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.

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

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.

推荐答案

这里最难的要求是:

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

The submitted Runnable is guaranteed to be executed exactly once.

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

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.

因此对于使用 Java 8 或更低版本的代码,我会说答案是不,这样的 invokeAndWait 方法是不可能的";(除了可能使用本机代码).

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

然而,Java 9 添加了内存模式 Opaque.页面使用 JDK 9 内存顺序模式"JEP 193(添加了此功能)的作者 Doug Lea 很好地描述了这一点细节.最重要的是,Opaque 模式比 volatile 弱,但仍提供以下保证:

However, Java 9 added the memory mode Opaque. The page "Using JDK 9 Memory Order Modes" by Doug Lea, the author of JEP 193 (which added this functionality), describes this in great detail. Most importantly Opaque mode is weaker than volatile but provides still the following guarantee:

  • 进展.写入最终可见.
    [...]
    例如,在某些变量 x 的唯一修改是让一个线程以不透明(或更强)模式写入的结构中,X.setOpaque(this, 1),任何其他线程在 while(X.getOpaque(this)!=1){} 最终会终止.
    [...]
    请注意,此保证不适用于普通模式,其中自旋循环可能(并且通常会)无限循环 [...]
  • Progress. Writes are eventually visible.
    [...]
    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.
    [...]
    Note that this guarantee does NOT hold in Plain mode, in which spin loops may (and usually do) infinitely loop [...]

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

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 §17.4.4). So the worker thread must be started before the action is constructed.

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

Additionally the "final field semantics" (JLS §17.15.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.

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

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

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

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 can help with this.

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

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.

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);
    }
  }
}

这篇关于Java 发生在关系 invokeAndWait 之前的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆