Local AtomicReference and array with lambda
Asked Answered
P

1

5

I try change and get local variable with lambda. I know I should use effectively final for local variables in lambda. When I use AtomicReference local variable changing failed:

    public class Lamb {
    public static void main(String[] args) throws InterruptedException {
        Lamb lamb = new Lamb();
        GlobalL globalL = new GlobalL();
        lamb.a(globalL);
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                globalL.printSum();
            }).start();
        }
        Thread.sleep(3000);
        System.out.println("--------After Work--------");
        globalL.printSum();
    }
    public void a(GlobalL globalL) {
        AtomicReference<Integer> number = new AtomicReference<>(0);
        Work work = () -> {
            number.getAndSet(number.get() + 1);
            return number.get();
        };
        globalL.setWork(work);
    }
}
class GlobalL {
    private Work work;
    public void printSum() {
        System.out.println(work.getAndInc());
    }
    public void setWork(Work work) {
        this.work = work;
    }
}
interface Work {
    int getAndInc();
}

Output different every time:

  1. --------After Work--------
    97
  2. --------After Work--------
    99
    When I change Atomic to array this working fine:
public void a(GlobalL globalL) {
        Integer[] number = {1};
        Work work = () -> {
            number[0]++;
            return number[0];
        };
        globalL.setWork(work);
}

Output every time:
--------After Work--------
102

  1. What's going on with array and atomic this situation?
  2. How to work anonymous class and labmda with non final local variable?
  3. How jvm works with lamda?
Pigpen answered 26/2, 2020 at 6:46 Comment(3)
Your three questions don't really seem related to each other. (And #3 is way too broad.) I recommend downscoping this question to just #1, and asking #2 as a separate question.Bobinette
You have a race condition on number.getAndSet(number.get() + 1); return number.get(). Use return number.incrementAndGet()Viridescent
@Alex in fact, the OP has two race conditions in a row. But it’s important to emphasize that the array variant has even more race conditions.Zing
F
6

1) The code:

 number.getAndSet(number.get() + 1);
 return number.get();

is a critical section since there is a couple of operations that are not atomically performed. That's why you get different results. To eliminate the critical section:

public void a(GlobalL globalL) {
    AtomicInteger number = new AtomicInteger(0);
    Work work = () -> {
        return number.incrementAndGet();
    };
    globalL.setWork(work);
}

2) You can't (see this or the official tutorial on Anonymous Classes)

3) IMO, it should be a separate question. To put it in few words, lambdas are just syntactic sugar and they get compiled into anonymous inner classes.


As for why array works correctly? question, the answer is: it doesn't for the same reason: ++ is not an atomic operator To prove it, just increase the number of threads to, let's say, 1000:

   for (int i = 0; i < 1000; i++) {
        new Thread(() -> {
            globalL.printSum();
        }).start();
    }

I'm getting:

--------After Work-------- 972

Falls answered 26/2, 2020 at 7:22 Comment(3)
@Pigpen thread safety has a price. It can make threads slower, so the array variant is not correct, but executing faster, which raises the chance that each of your threads completes before the next one even started. In that scenario, there is no concurrency and the lack of thread safety does not influence the result. But as this answer says correctly, the ++ operator is not thread safe and you just have been lucky. Also, Thread.sleep(3000); does not guaranty the completion of the threads nor memory visibility of the result, printSum() printing the end sum is pure luck either.Zing
@Zing and Eugen Covaci you are right I'm wrong. I "just have been lucky" for 100 threads. Thanks!Pigpen
"lambdas are just syntactic sugar and they get compiled into anonymous inner classes" is not correct. They do not create anonymous classes; they are essentially member methods of the declaring class. They do not create a new inner scope.Boschbok

© 2022 - 2024 — McMap. All rights reserved.