0

Using ExecutorService I submit a batch of tasks and tasks have a time variable, namely GENERAL_TIME share between them. I want to set the value of GENERAL_TIME just before submitting tasks. Here is the code:

ExecutorService executor = Executors.newWorkStealingPool();
    ArrayList<Callable<Boolean>> callables = new ArrayList<>();
    long GENERAL_TIME = 0;
    for (String i : host.getHosts()){
        callables.add(
                () -> {
                    ESBRun.monitorOSMetrics(
                            db, COMPONNENT_TYPE, GENERAL_TIME, metric, ssh, i
                    );
                    return true;
                }
    }
    GENERAL_TIME = System.currentTimeMillis();
    executor.invokeAll(callables)
            .stream()
            .map(future -> {
                try {
                    return future.get();
                }
                catch (Exception e) {
                    throw new IllegalStateException(e);
                }
            });
}

but it errors

variable used in lambda should be final or effectively final java

How can I solve that?

Soheil Pourbafrani
  • 3,249
  • 3
  • 32
  • 69
  • Where's the code that's causing that error? This statement `GENERAL_TIME = System.currentTimeMillis();` is not in a lambda expression and cannot cause that compile error – ernest_k Mar 03 '18 at 18:50
  • the post edited. I call a function that uses `GENERAL_TIMESTAMP` as an argument. this line cause so-call error. – Soheil Pourbafrani Mar 03 '18 at 18:55
  • Thx. The use of `GENERAL_TIME` isn't clear. Are you intending to time the duration of the execution of all threads? – ernest_k Mar 03 '18 at 19:00
  • I'm pretty sure lambdas were designed to NOT work this way. GENERAL_TIME is captured at the point the lambda is created (`callables.add()`) and the spec design prevents any other type of capture. – markspace Mar 03 '18 at 19:05

1 Answers1

2

Your variable is defined on a stack, which would normally be garbage collected after the method quits. However, using the value in a lambda requires Java to share the value in a closure, which requires it to be final or effectively final (not modified by any code it is shared to). Any value to remain mutable across threads must be, therefore, wrapped into an instance which itself is final or effectively final.

The easiest, and also thread safe, way to achieve this is by using classes from java.util.concurrent.atomic, in your case AtomicLong. The variable itself will be effectively final or can be declared final, but the value can be set (and read) in any lambda and thread:

// declaration
final AtomicLong generalTime = new AtomicLong(0);

// usage acr
generalTime.set(System.currentTimeMillis()));
generalTime.get();
Oleg Sklyar
  • 9,834
  • 6
  • 39
  • 62
  • This seems like a good solution, with only a little bit of work re-coding for the OP. – markspace Mar 03 '18 at 19:06
  • I often use this very same technique to share values in reactive code and across lambdas. – Oleg Sklyar Mar 03 '18 at 19:10
  • I tend to use immutable objects to share information between threads. Your answer seems like a good general pattern when adapting code and algorithms from other languages. – markspace Mar 03 '18 at 19:12