Java – Is Using Runnable Outside Multithreading Context Valid?

java

Inspired by this SO question, I'd like to better understand the contract defined by Runnable, along with when it is and is not acceptable to use the Runnable interface. We know that Runnable's most common use-case is to be implemented by a class that is intended to be executed on a separate thread:

Runnable task = () -> {
    // Code to be executed on another thread
};
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(task);

This yields two questions:

  1. Is this the only acceptable pattern for its usage?
  2. If not, what are other acceptable usage patterns for Runnable that do not revolve around multithreading?

The use-case in the original SO question is to create a generic way to execute code before and after a method's execution without repetition, such as:

public void example1() {
    before();
    method1();
    after();
}

public void example2() {
    before();
    method2();
    after();
}

Eran's answer proposes using Runnable to allow encapsulation of the logic for each method, along with a single method that accepts the Runnable as an argument and invokes Runnable#run at the appropriate time:

public void call(Runnable method){
    before();
    method.run();
    after();
}

call( () -> { method1(); });
call( () -> { method2(); });

However, using Runnable in this fashion was met with some disagreement, stating that this is not an acceptable use of Runnable. Is this is a valid concern, or a matter of stylistic opinion?

From my own research, the Javadoc for Runnable states the following contract, which seems to still be upheld with this usage of Runnable:

The Runnable interface should be implemented by any class whose instances are intended to be executed by a thread.

Further, if usage outside of the context of multithreading were discouraged or disallowed, I would expect either:

  1. The Javadoc to mention it somewhere, or
  2. Runnable to be in a concurrency-specific package, e.g. java.util.concurrent, however it is available in java.util.

Best Answer

While it is still clearly a matter for debate among the community, Alex revealed a comment left by Brian Goetz, Java Language Architect at Oracle, which I believe is the definitive canonical answer to this question. I've copied his comments below, emphasis mine:

The EG discussed this in some detail and concluded that it was better to coopt Runnable than to create the opportunity for confusion because there were two competing abstractions. In a perfect world, we might have rewritten the Javadoc to reflect that its use had (long ago) become broader than was imagined in 1995, but the reality is that well before 2014 it became broadly understood that Runnable was the JDK's primary "task" abstraction, regardless of its connection with threads.