Java happens-before relationship invokeAndWait
Asked Answered
S

1

4

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.

Solan answered 23/10, 2020 at 9:26 Comment(3)
As curiosity, why you want the calculation done in a specific thread, but you are also blocking the calling thread?Aldehyde
Not related to the question itself, but you may want to execute code, which is not thread safe, on a specific thread only and then wait for the result on another thread. I think that's also the use case for the SwingUtilities.invokeAndWait(java.lang.Runnable) function.Solan
Ok, so it is to prevent parallelism on that calculation..Aldehyde
P
2

The most difficult requirement here is:

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 "No, such an invokeAndWait method is not possible" (except maybe using native code).

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:

  • 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 [...]

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.

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.

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.

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);
    }
  }
}
Peppard answered 31/10, 2020 at 23:8 Comment(2)
It is possible to implement such a behavior prior to Java 9, without its opaque mode. The key point has been described over and over again in articles of the “Why double-checked locking is broken (without volatile) in Java” kind. This anti-pattern is precisely capable of producing a non-null value for sure, without an actual happens-before relationship.Filose
@Holger, you are probably more knowledgeable in this area than me. Would you mind adding a separate answer which describes this? Prior to Java 9 is it really possible to satisfy the "guaranteed to be executed exactly once" requirement by OP (I understood that as implied "executed eventually")?Peppard

© 2022 - 2024 — McMap. All rights reserved.