Effectively final

Brian Goetz brian.goetz at oracle.com
Fri Jul 29 11:08:09 PDT 2011


You are right to have these concerns.

The canonical example is:

int fooSum = 0;
list.forEach(#{ x -> sum += x.getFoo() });

You are correct that there are purely sequential use cases that benefit 
from this approach, which are not subject to data races.  (On the other 
hand, it is nearly impossible to write the above primitive-using code so 
that it is not subject to data races in a parallel environment.)  We 
have explored approaches of capturing this constraint in the language, 
so that we could prevent or detect when such a "thread-confined" lambda 
is used from the wrong thread.  While these are likely feasible, they 
add complexity.

Examples like the above have been around for 50+ years.  However, it is 
worth noting that they became popular in the context of a sequential, 
uni-processor world.  Rather than expend energy and introduce additional 
complexity to prop up an aging and increasingly irrelevant programming 
idiom, we instead are directing our energies towards providing more 
modern, parallel-friendly idioms, like:

   int fooSum = list.reduce(0, Reducers.INT_SUM);

which is more compact, more readable (once you learn what "reduce" 
means), less error-prone, and can be parallelized by the library.

You state that "most languages that support closures do allow capture of 
mutable local variables."  However, most of them have {little,no,bad} 
support for safe concurrency or parallelism.  If Java were a strictly 
sequential language, the story might be different, but that isn't the 
reality -- or desirable.

We had three basic choices here:
  1.  Allow arbitrary mutable capture, and leave it to the user to not 
create data races.
  2.  Complicate the language further by introducing a notion of 
thread-confined lambdas, and allow mutable local capture by 
thread-confined lambdas.
  3.  Prohibit mutable local capture entirely.

To do (1) in 2011 would be foolish; this effectively opens a whole new 
category of concurrency hazards -- data races on locals -- that didn't 
exist before.  If I know anything about concurrency in Java, I know that 
it is already too hard to write correct concurrent code and to verify 
that concurrent code is correct.  (Local variables are one bastion of 
safety in this analysis.)  Making it harder would be dumb; making it 
harder just to prop up an aging idiom that has good alternatives would 
be dumber.

We could do (2) but we felt that we were better off delivering a simpler 
language feature earlier rather than complicating/delaying to support 
this use case.

So we chose (3), which still provides the option to do (1) or (2) in the 
future.

> I was just looking at Brian Goetz's proposal "State of the Lambda".
> Firstly, I'd like to say that adding lambdas/closures to the Java
> language will be a huge win. However I do have some concerns with the
> proposal that lambdas will not be able to modify captured local variables.
>
> To give some background, the angle I am coming from is someone who is
> writing an asynchronous programming framework which makes have use of
> callbacks. Similar frameworks exist in other languages, e.g. node.js is
> a very popular one right now.
>
> AIUI most languages which support closures/lambdas do allow local
> variables to be mutated from within the closure. This allows a local
> variable to co-ordinate the results from different callbacks and allows
> asynchronous control flow to be composed in a clear and simple way.
>
> I fully understand that if multiple threads are involved then allowing
> such access can result in race conditions. However these frameworks
> typically guarantee that all access to a co-ordinated set of objects is
> accessed by at most one thread (event loop / reactor pattern), so race
> conditions are not an issue.
>
> Right now, because of lack of lambdas/closures such a style of
> programming is clunky using Java (since anonymous classes are required),
> but even with the proposed lambdas it would still be difficult since it
> would require co-operating closures to interact via final object
> references (e.g. using AtomicLong, AtomicReference or some other
> object), which will result in ugly and clunky code (compared to other
> programming languages). This seems really unnecessary when the framework
> can guaranteed the same thread always executes the code.
>
> IMHO it would be nice, and a big win for such a programming style, to,
> perhaps default to final variable access only (for safety with multiple
> threads), but to allow this rule to be relaxed when the framework can
> guarantee that thread safety is not a concern, thus allowing much
> simpler code.
>
>
>


More information about the lambda-dev mailing list