Runnables vs. Method References and Garbage

Viewed 523

A relatively recent published article about replacing executors by actors in Java 8 stated that using an anonymous inner class Runnable like so:

// Functional operation
executor.execute(new Runnable() {
    System.out.println(data);
});

Can be ideally replaced with a more garbage-collector friendly technique performing the same operation:

Actor<String> actor = new Actor<>(parentExecutor, ::onMessage);

// Equivalent functional operation
actor.act("Hello world");

public void onMessage(String message) {
   System.out.println(message);
}

It makes sense that one should generally use method references over anonymous inner classes (i.e. new Runnable, new Callable). That idea has been around for a while. There seems to be another subtlety here using this Actor pattern that's more efficient, but not explicitly explained in the article.

I understand that using:

executor.execute(() -> System.out.println(data));

Will be apparently inefficient (albeit, slightly) because the compiler has to create a generated method invokedynamic "call site" and reference it when invoked.

Is the advantage here that (1) a direct method reference doesn't have the overhead of the invokedynamic method generation and (2) can additionally allows you to pass method parameters to this direct reference? What is the key takeaway of this idea, presented in the article, over the patterns we already use and know?

1 Answers

The article doesn’t say at any place that using a lambda expression instead of the method reference would be worse.

The key idea is that instead of calling

executor.execute(new Runnable() {
    public void run() {
        System.out.println(data);
    }
});

millions of times, creating millions of Runnable instances, you do

Actor<String> actor = new Actor<>(parentExecutor, this::onMessage);

once and call

actor.act(data);

millions of times. The Actor uses a queue of strings (or whatever data item you use) internally, not wrapping any item into another object¹, and a single Runnable to enqueue.

The same would work as well when doing

Actor<String> actor = new Actor<>(parentExecutor, x -> System.out.println(x));

instead or even

Actor<String> actor = new Actor<>(parentExecutor, new ActorListener<String>() {
    public void onMessage(String message) {
        System.out.println(message);
    }
});

as that’s the code that is executed only once, so its technical details are irrelevant.

Lambda expressions or method references make using this pattern easier, but are not the cornerstone of this pattern. And the article doesn’t say otherwise. All it says is “With the usage of lambdas it gets really elegant


¹ to be nitpicking, it uses a ConcurrentLinkedQueue behind the scenes, which does wrap each item into a node object, demonstrating, how irrelevant temporary objects might be. Using an array based queue would be more consistent, but then, the queue couldn’t be non-blocking. You can’t have the cake and eat it too…

Related