Java happens-before 关系 invokeAndWait
Java happens-before relationship invokeAndWait
我的问题与this问题相关,该问题已有答案:
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
,使其 正常工作 ,但不强加happens-before 关系?通过方法正常工作我的意思是:
- 提交的
Runnable
保证恰好执行一次
- 提交的
Runnable
在特定线程上执行。
- 该方法等待提交的
Runnable
执行完成。
- 提交的
Runnable
执行完成后,该方法保证 return。
对我来说,如果不强加 happens-before 关系,似乎无法实现这一点,或者我错了?如果是这样,请提供一个示例实现来证明这一点。
这里最难的要求是:
The submitted Runnable
is guaranteed to be executed exactly once.
使用非volatile
(Plain) 字段将工作任务从提交者转移到执行者不会创建 happens-before 关系,但也不能保证执行者完全或在有限时间内看到任务。编译器将能够优化对该字段的分配,或者在运行时执行程序线程可能只从其缓存而不是主内存中读取值。
因此对于使用 Java 8 或更低版本的代码,我会说答案是“不,这样的 invokeAndWait
方法是不可能的”(除了可能使用本机代码)。
不过,Java9 增加了内存模式Opaque。页面“Using JDK 9 Memory Order Modes" by Doug Lea, the author of JEP 193(添加了此功能)对此进行了非常详细的描述。最重要的是 Opaque 模式比 volatile
弱,但仍提供以下保证:
- 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)。所以工作线程必须在构造action之前启动。
此外还必须考虑“final
字段语义”(JLS §17.15.1)。当 invokeAndWait
的调用者以 lambda 表达式的形式创建 Runnable
时,该 lambda 对变量的捕获(据我所知)具有隐式 final
字段语义。
If so, please include an example implementation, which proves this.
使用示例证明或反驳 thread-safety 或 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);
}
}
}
我的问题与this问题相关,该问题已有答案:
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
,使其 正常工作 ,但不强加happens-before 关系?通过方法正常工作我的意思是:
- 提交的
Runnable
保证恰好执行一次 - 提交的
Runnable
在特定线程上执行。 - 该方法等待提交的
Runnable
执行完成。 - 提交的
Runnable
执行完成后,该方法保证 return。
对我来说,如果不强加 happens-before 关系,似乎无法实现这一点,或者我错了?如果是这样,请提供一个示例实现来证明这一点。
这里最难的要求是:
The submitted
Runnable
is guaranteed to be executed exactly once.
使用非volatile
(Plain) 字段将工作任务从提交者转移到执行者不会创建 happens-before 关系,但也不能保证执行者完全或在有限时间内看到任务。编译器将能够优化对该字段的分配,或者在运行时执行程序线程可能只从其缓存而不是主内存中读取值。
因此对于使用 Java 8 或更低版本的代码,我会说答案是“不,这样的 invokeAndWait
方法是不可能的”(除了可能使用本机代码)。
不过,Java9 增加了内存模式Opaque。页面“Using JDK 9 Memory Order Modes" by Doug Lea, the author of JEP 193(添加了此功能)对此进行了非常详细的描述。最重要的是 Opaque 模式比 volatile
弱,但仍提供以下保证:
- 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 inwhile(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)。所以工作线程必须在构造action之前启动。
此外还必须考虑“final
字段语义”(JLS §17.15.1)。当 invokeAndWait
的调用者以 lambda 表达式的形式创建 Runnable
时,该 lambda 对变量的捕获(据我所知)具有隐式 final
字段语义。
If so, please include an example implementation, which proves this.
使用示例证明或反驳 thread-safety 或 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);
}
}
}