19

I know that for concurrency reasons I cannot update the value of a local variable in a lambda in Java 8. So this is illegal:

double d = 0;
orders.forEach( (o) -> {
     d+= o.getTotal(); 
});

But, what about updating an instance variable or changing the state of a local object?, For example a Swing application I have a button and a label declared as instance variables, when I click the button I want to hide the label

 jButton1.addActionListener((  e) -> {
      jLabel.setVisible(false);
 });

I get no compiler errors and works fine, but... is it right to change state of an object in a lambda?, Will I have concurrency problems or something bad in the future?

Here another example. Imagine that the following code is in the method doGet of a servlet Will I have some problem here?, If the answer is yes: Why?

String key = request.getParameter("key");

Map<String, String> resultMap = new HashMap<>();  

Map<String, String> map = new HashMap<>();
//Load map

map.forEach((k, v) -> {
    if (k.equals(key)) {
        resultMap.put(k, v);
    }
});
 response.getWriter().print(resultMap); 

What I want to know is: When is it right to mutate the state of an object instance in a lambda?

1

3 Answers 3

15

Your assumptions are incorrect.

You can only change effectively final variables in lambdas, because lambdas are syntactic sugar* over anonymous inner classes.
*They are actually more than only syntactic sugar, but that is not relevant here.

And in anonymous inner classes you can only change effectively final variables, hence the same holds for lambdas.

You can do anything you want with lambdas as long as the compiler allows it, onto the behaviour part now:

  • If you modify state that depends on other state, in a parallel setting, then you are in trouble.
  • If you modify state that depends on other state, in a linear setting, then everything is fine.
  • If you modify state that does not depend on anything else, then everything is fine as well.

Some examples:

class MutableNonSafeInt {
    private int i = 0;

    public void increase() {
        i++;
    }

    public int get() {
        return i;
    }
}

MutableNonSafeInt integer = new MutableNonSafeInt();
IntStream.range(0, 1000000)
        .forEach(i -> integer.increase());
System.out.println(integer.get());

This will print 1000000 as expected no matter what happens, even though it depends on the previous state.

Now let's parallelize the stream:

MutableNonSafeInt integer = new MutableNonSafeInt();
IntStream.range(0, 1000000)
        .parallel()
        .forEach(i -> integer.increase());
System.out.println(integer.get());

Now it prints different integers, like 199205, or 249165, because other threads are not always seeing the changes that different threads have made, because there is no synchronization.

But say that we now get rid of our dummy class and use the AtomicInteger, which is thread-safe, we get the following:

AtomicInteger integer = new AtomicInteger(0);
IntStream.range(0, 1000000)
        .parallel()
        .forEach(i -> integer.getAndIncrement());
System.out.println(integer.get());

Now it correctly prints 1000000 again.
Synchronization is costly however, and we have lost nearly all benefits of parallelization here.

Sign up to request clarification or add additional context in comments.

Comments

5

In general: yes, you may get concurrency problems, but only the ones you already had. Lambdafying it won't make code non-threadsafe where it was before, or vice versa. In the example you give, your code is (probably) threadsafe because an ActionListener is only ever called on the event-dispatching thread. Provided you have observed the Swing single-threaded rule, no other thread ever accesses jLabel, and if so there can be no thread interference on it. But that question is orthogonal to the use of lambdas.

8 Comments

What do you mean with 'orthogonal to the use of lambdas'?
@NestorHernandezLoli your code with the map is fine because it is single threaded. However you are mixing two concepts: lamdas and streams: your initial example is a simple lambda which, as Maurice points out, is as safe as the non lambda equivalent. Your second example is a mutation in the terminal operation of a stream: that may cause issues if the steam is parallel and your mutation isn't thread safe.
@NestorHernandezLoli @assylias I'm not sure I understand assylias's comment. The servlet example is safe because the only references to the maps are local variables, so they can't be accessed by any other thread. But you're off topic: this new example has nothing to do with lambdas, and your original question has little to do with them. That's why I called the issues orthogonal: putting code into a lambda doesn't alter which thread runs it. The only connection that I can see is that if a lambda is a parameters to a Stream method, the framework decides which thread to run them in.
Maurice I modified the title of my question. Your answer is very useful, but I really want to understand when it is safe to mutate an object instance in a lambda. Please try to be more clear, many of us don't understand some technical jargon.
It's safe to mutate an object instance in a lambda under exactly the same circumstances as it would be safe to do it anywhere else. The fact that the code is in a lambda makes no difference; the question of which thread is executing the code is the question that matters. If that isn't clear to you, I don't think we can clear it up in a comment thread: you'll have to do some background reading. Best is "Java Concurrency in Practice"; if you don't want a whole book try freejavaguide.com/java-threads-tutorial.pdf, especially Section 5.
|
2

in case 'forEach' is distributed to different threads/cores you might have concurrency issues. consider using atomics or concurrent structures (like ConcurrentHashMap)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.