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?
Abdullajon
  • 366
  • 3
  • 12
  • 2
    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. – ruakh Feb 26 '20 at 06:50
  • You have a race condition on number.getAndSet(number.get() + 1); return number.get(). Use return number.incrementAndGet() – Alex Sveshnikov Feb 26 '20 at 06:56
  • 2
    @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. – Holger Feb 26 '20 at 09:53

1 Answers1

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

  • 3
    @Abdullajon 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. – Holger Feb 26 '20 at 09:57
  • @Holger and Eugen Covaci you are right I'm wrong. I "just have been lucky" for 100 threads. Thanks! – Abdullajon Feb 26 '20 at 10:35
  • "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. – Ti Strga Mar 04 '21 at 01:12