1

I don't understand how the JMM qualifies the usage of local variables inside lambdas (and also in local and anonymous classes).

It looks like they aren't "shared variables" in terms of the JLS:

Local variables (§14.4), formal method parameters (§8.4.1), and exception handler parameters (§14.20) are never shared between threads and are unaffected by the memory model.

But what are they then?

For example, could the following code print 0?

public class Test1 {
  
  static Runnable r = null;
  
  public static void main(String[] args) throws Exception {
    var t = new Thread(Test1::thread1);
    t.start();
    r = createLambda();
    t.join();
  }
  
  static void thread1() {
    Runnable localR;
    while((localR = r) == null) {
      Thread.onSpinWait();
    }
    localR.run();
  }
  
  static Runnable createLambda() {
    var i = 10;
    return () -> {
      System.out.println(i); // could it print 0?
    };
  }
}

As far as I understand, if i inside createLambda() was a shared variable, then 0 could be printed (a default value for int variable) because there is no happens-before between var i = 10; in main() thread and System.out.println(i) in thread1() thread.

But if i is not a shared variable, then I don't know what is possible here.


!(UPDATE for moderators) I don't think this question is a duplicate of that question.
I guess you marked this question as a duplicate because you think that the answer to that question answers my question as well (because the texts of the questions differ significantly).
But it doesn't: the main thing that the answer misses is proofs (i.e. links to and citations from the specs, the javadocs etc.). Without official proofs the generated classes with final fields described in the answer could be just an internal detail of the current implementation, which could change at any time.
And actually it would be much better if there were proofs at the level of the Java language: that's because both the JMM (which I specifically asking about) and lambdas are defined at the language level (here and here), but the answer describes the level of the JVM - this is kind of weird when in order to find out how the two parts of the JLS are related to each other we have to read the (lower-level) JVM standard.


UPDATE (08 May 2023) User Holger in the comments provided the link to this answer, which answers this question.

  • 1
    How could it ever print `0`? You initialize `i` to `10` and then never reassign it (not that you could reassign it, as local variables used inside a lambda expression or anonymous class must be final or effectively final). – Slaw Apr 29 '23 at 18:31
  • If you're wondering how it could print `10` instead of just random garbage given the variable `i` is "destroyed" once `createLambda()` returns, that's because lambda expressions and anonymous classes "capture" local variables. Effectively, the class created to implement the lambda expression/anonymous class has a final field that is initialized with the value of the local variable. – Slaw Apr 29 '23 at 18:35
  • IIRC it is strictly equivalent to having a `final` local variable that is then passed to a constructor of the `Runnable` object (where it is copied to a final field). – Louis Wasserman Apr 29 '23 at 18:38
  • @Slaw "How could it ever print 0?" in Java it's sometimes possible when initialization of the variable happens in one thread and the read happens in another thread: the initialization actually consists of two writes (the default value `0`, then `10`) and the read in another thread might return any of the two writes unless a special happens-before relation is established between the initialization and the read. It's mentioned in [the language spec](https://docs.oracle.com/javase/specs/jls/se17/html/jls-17.html). –  Apr 29 '23 at 18:58
  • @Slaw "Effectively, the class created to implement the lambda expression/anonymous class has a final field that is initialized with the value of the local variable" is this officially guaranteed anywhere, or is this just an internal detail of the current implementation which could change at any moment? –  Apr 29 '23 at 19:02
  • @LouisWasserman "IIRC it is strictly equivalent to having a final local variable that is then passed to a constructor of the Runnable object (where it is copied to a final field)" I think I heard something along these lines, but is this officially guaranteed anywhere (specs, javadocs, etc.)? –  Apr 29 '23 at 19:04
  • @user16320675 the lambda uses the variable `i` - how is that "not the same variable as `i`". –  Apr 29 '23 at 19:08
  • @user16320675 AFAIK it's something like "the variable is captured in the context" or something like that. It's the same idea as [closure](https://en.wikipedia.org/wiki/Closure_(computer_programming)), which is used in many programming languages. –  Apr 29 '23 at 19:15
  • I'm not sure if capturing the local variable is part of the specification or just the implementation. I've been looking but have not found anything as of yet. However, note here that `i` is a local variable, so concurrency doesn't apply. Besides, the `Runnable` must have been created before you started the thread, and starting a thread creates a _happens-before_ relationship. In other words, with your current code, it's impossible for anything other than `10` to be printed. – Slaw Apr 29 '23 at 20:37
  • @Slaw look at the `main()` method: the thread is started first as `t.start();`, then the lambda is created and the reference is written to r `r = createLambda()`. And finally in another thread inside `thread1()` the `r` is read and the lambda is executed. Because the `createLambda()` is executed after the thread is started, there is no happens-before there. –  Apr 29 '23 at 21:23
  • Ah you're right. Sorry, for whatever reason I thought you were doing `new Thread(createLambda())`. But the conclusion is the same. It's not possible for `i` to be anything other than `10` when you print it. However, this is something I "just know" from writing multi-threaded code in Java and I don't have official proof right now. – Slaw Apr 29 '23 at 21:50
  • 1
    *"Without official proofs the generated classes with final fields described in the answer could be just an internal detail of the current implementation, which could change at any time."* - If that is what you are asking for you should ask for it explicitly. Edit your question so that that it clearly isn't a duplicate. (I am reading the "update for moderators" section as an extended comment rather than as part of the question.) – Stephen C Apr 30 '23 at 04:10
  • 2
    The point is ... it is not up to *us* to rewrite your question. In fact, we are *discouraged* from making changes to questions that materially alter what they are asking. If you want the question reopened so that it can get a proper answer, it is up to you to do the editing. – Stephen C Apr 30 '23 at 04:16
  • I think the misinterpretation is that _the lambda uses the variable `i`_. While the code looks like that the lambda effectively just uses the **value** of the variable `i` (i.e. during creation of the lambda the value is copied and stored somewhere within the created object). This is **the only way** that Java can guarantee that the lambda works. Consider the simple case: `r = createLambda(); r.run();` - after `createLambda()` returns the local variable `i` is gone, but `r.run()` has to somehow access its value. That can only be done if `r` has stored **the value** somewhere. – Thomas Kläger Apr 30 '23 at 06:30
  • @ThomasKläger when we look at the code we see that the variable `i` is used inside the lambda `()->{System.out.println(i);}` that's why i wrote _the lambda uses the variable `i`_. I'm aware that in Java these variables must be final or effectively final, I guess this fact indeed makes it possible for jvm implementations to copy the variable's value into the lambda. –  Apr 30 '23 at 12:16
  • 1
    @ThomasKläger regarding _**the only way**_ I disagree: lambdas could be implemented differently. Look for example at [lambdas in C#](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/lambda-expressions#capture-of-outer-variables-and-variable-scope-in-lambda-expressions): you can write into the outer variables from inside the lambda. –  Apr 30 '23 at 12:17
  • @ThomasKläger Anyway, my question is not about the implementation details of lambdas in Java. I'm actually asking what `i` is from the perspective of [the JMM](https://docs.oracle.com/javase/specs/jls/se17/html/jls-17.html#jls-17.4) (regardless of how the lambdas are implemented internally). I'm asking about that because the JMM defines the rules for cases when variables are shared between different threads - and according to the JMM it's possible to observe [the weirdest behaviour](https://shipilev.net/blog/2016/close-encounters-of-jmm-kind/) in such cases. –  Apr 30 '23 at 12:35
  • @ThomasKläger `i` in the example is seemingly accessed from multiple threads, and therefore the rules of the JMM should be applicable to the example (and the wierdest behaviour might be possible in the example). But I cannot find anything about lambdas in the JMM. –  Apr 30 '23 at 12:35
  • @StephenC _Edit your question so that that it clearly isn't a duplicate._ The questions have very different text from the beginning. This question is: "How does the JMM qualifies the usage of local variables inside lambdas?" The [other question](https://stackoverflow.com/q/61075400/21774510) is: "I can't think why the captured variables are final or effectively final in lambda expressions. ... What is this variable capture? ... In this particular code I want know the reasons why ... Also i like to know why we can mutate this.instance too" –  Apr 30 '23 at 12:45
  • 1
    @algernon.zakary-donebyngle-com - I don't agree. Maybe you can convince someone else to open the question for you. – Stephen C Apr 30 '23 at 13:14
  • 1
    The JMM doesn't "qualify the usage of local variables inside lambdas" - it doesn't need to because the JVM doesn't support this. The JVM has no instruction that would allow another thread (or even another method running on the same thread) to access a local variable of a method. The Java compiler and the JVM work together to create the illusion that you can access a local variable from a lambda. I write "illusion" because the rules of Java allow it to copy the value a local variable holds somewhere else (presumable into the object created for the lambda) so that the lambda can access that copy – Thomas Kläger Apr 30 '23 at 15:46
  • That other languages (like C#) have different rules doesn't matter here: your question is about Java, the JMM and the rules for them. – Thomas Kläger Apr 30 '23 at 15:47
  • @ThomasKläger, The conversation wasn't about implementation details, it was about what it means for OP's lambda body to refer to the variable, `i`. When you said, "**the only way**," you made it sound as if the Java Language Specification's authors had no choice when they said that `i` must be effectively final. But, they _did_ have a choice. They could have decided to make lambda captures work differently. E.G., They could have decreed that the implementation must create a _lexical closure_ every time a block containing a lambda is entered. It's non-negotiable today, but there was a time... – Solomon Slow May 01 '23 at 01:30
  • 3
    I think [this answer](https://stackoverflow.com/a/66674785/2711488) is the best you can get so far. – Holger May 08 '23 at 10:58
  • Your question is about how the JMM does it, and as @ThomasKläger has correctly pointed out, the JMM doesn't do it at all. The compiler does it, all as specified in the Java Language Specification. If you now consider that an implementation detail that's an error in your question. I've added the other duplicate. – user207421 May 09 '23 at 00:28
  • @user207421 Thank you for adding the link to [the Holger's answer](https://stackoverflow.com/a/66674785) - this is the perfect answer to my question, it explains everything I wanted to know. [The answer provided by ThomasKläger](https://stackoverflow.com/a/61075832) is not what I asked about. On the most basic level is doesn't mention the JMM, which I explicitly asked about. And yes, it describes implementation details (which I didn't ask for): I'm confident because (to be sure I understand ThomasKläger) I've dived pretty deeply in the implementation of lambdas in Java and re-read the JLS. –  May 10 '23 at 16:56

0 Answers0