5

In the book Java Concurrency In Practice, there is this example of an almost immutable object which is at risk of failure if not properly published:

// Taken from Java Concurrency In Practice
// p.51 Listing 3.15: Class at risk of failure if not properly published.
public class Holder {
    private int n;

    public Holder(int n) { this.n = n; }

    public void assertSanity() {
        if(n != n)
            throw new AssertionError("This statement is false.");
    }
}

// p.50 Listing 3.14: Publishing an object without adequate synchronization. Don't do this.
class Client {
    public Holder holder;

    public void initialize() {
        holder = new Holder(42);
    }
}

If I understand the chapter in the book correctly, adding final to the n field of the Holder class will make the object completely immutable and eliminate the chance of getting the AssertionError thrown even if it's still published without adequate synchronization like it's done in the Client class.

Now I'm wondering how anonymous classes behave in this respect. Please see the following example:

public interface IHolder {
    void assertSanity();
}

class IHolderFactory {
    static IHolder create(int n) {
        return new IHolder() {
            @Override
            public void assertSanity() {
                if (n != n)
                    throw new AssertionError("This statement is false.");
            }
        };
    }
}

class IHolderClient {
    public IHolder holder;

    public void initialize() {
        // is this safe?
        holder = IHolderFactory.create(42);
    }
}

It's published without adequate synchronization just like in the example from the book, but the difference is that now the Holder class has become an interface and there is a static factory method which returns an anonymous class implementing the interface, and the anonymous class uses the method parameter n.

My question is: is there any chance of getting the AssertionError from my latter example thrown? If there is, what is the best way to make it completely immutable and eliminate the problem? Does it change something if it was written in a functional way like the following?

class IHolderFactory {
    static IHolder create(int n) {
        return () -> {
            if (n != n)
                throw new AssertionError("This statement is false.");
        };
    }
}
Holger
  • 285,553
  • 42
  • 434
  • 765
Kohei Nozaki
  • 1,154
  • 1
  • 13
  • 36

2 Answers2

7

This is a very tricky issue.

JLS, §17.4.1. Shared Variables says:

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.

This seems to contradict the fact that you can use them within an inner class or lambda expression that can be shared between threads, but those constructs capture the value of the variable and use the value. This process, however, is not very well specified.

The only mentioning I could ever find, is in §15.27.2 explaining the (effective) final requirement:

The restriction to effectively final variables prohibits access to dynamically-changing local variables, whose capture would likely introduce concurrency problems.

In practice, the captured values are stored in synthetic final fields of the inner class or the class generated at runtime for the lambda expression. So you will never see the error with the current implementation.

This, however, is not specified anywhere. The language specification says little about the bytecode format and the virtual machine specification says little about the language constructs.

So, local variables, formal method parameters, and exception handler parameters are explicitly excluded from the JMM and their captured values are not variables in the JMM’s regard and not even mentioned there. The question is what does that mean.

Are they generally immune to data races (my interpretation) or are they unsafe and we do no get any guaranty from the JMM at all? In the latter case, it would even imply that we were not able to make them safe, as any safe publication mechanism gets its safety from guarantees of the JMM which does not cover our case. It’s worth noting that the JMM also does not cover the outer this reference nor an instance’s implicit reference to a Class object returned by getClass().

So while I’d consider them immune to data races, I wish that was specified more explicit.

Holger
  • 285,553
  • 42
  • 434
  • 765
2

It does not matter if you use an anonymous class or lambda, you have zero synchronization mechanisms here to correctly publish the reference; as such, this code can throw that Exception.

In other words, there are tools and conditions that you must meet so that your code is safe: these are using final, volatile or some sort of synchronized or locks, etc. Since you use none, no guarantees are provided.

The Java Language Specification offers these guarantees only when you use special semantics, like final that you have shown in the first example. There are others too, but making an object immutable is the simplest, most trivial way. This is the best article that I am aware of on this subject, you might want to read it.

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • I'm not sure that's the "best article." It starts off by introducing two concepts the author calls "safe publication" and "safe initialization," and then immediately launches into a treatment of Singletons, the poster child for incorrect memory and concurrency semantics. Singletons are considered antipatterns in part because it's difficult to write them correctly. – Robert Harvey Mar 16 '21 at 16:14
  • @RobertHarvey you need to scroll down to [safe publication](https://shipilev.net/blog/2014/safe-public-construction/#_safe_publication) and [safe initialization](https://shipilev.net/blog/2014/safe-public-construction/#_safe_initialization) – Eugene Mar 16 '21 at 16:15
  • I did that already. My point is that you can dig deeply into these semantics (which from an academic standpoint is probably a good idea anyway), or you can simply avoid the situations where this kind of deep understanding of Java's memory model is required. – Robert Harvey Mar 16 '21 at 16:16
  • Does it help if I add `final` to the method parameter like this? `static IHolder create(final int n)` – Kohei Nozaki Mar 16 '21 at 22:48
  • 1
    @KoheiNozaki no it does not. – Eugene Mar 16 '21 at 23:35
  • I wonder why though.. If I'm not mistaken the reason why the AssertionError can be thrown in the example in the book is that initially, an int instance field is initialized to 0 and which is why some thread might read 0 before the constructor fully gets executed or while it's being executed. Does that mean that the same thing can happen to a method parameter as well? – Kohei Nozaki Mar 17 '21 at 09:08
  • @KoheiNozaki you should stop assuming things and start looking into the `JLS`. `final` must be placed on all _instance_ fields, to get a properly immutable object, _not_ on metod arguments, which is not going to help. – Eugene Mar 17 '21 at 10:18
  • 1
    You know, the specification does not guaranty that a lambda expression produces a new object. So in principle, it could result in an object created previously by an entirely different thread and the specification does not say that this establishes a *happens-before* relationship between that unknown creator and the current thread. Good luck explaining how to make the publication thread safe in this case, if we don’t assume lambda expressions to be intrinsically immutable. – Holger Mar 17 '21 at 14:28