Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a local variable when initializing a static variable

In the source code of java.util.Scanner I've found these static utility methods:

private static Pattern separatorPattern() {
    Pattern sp = separatorPattern;
    if (sp == null)
        separatorPattern = sp = Pattern.compile(LINE_SEPARATOR_PATTERN);
    return sp;
}

private static Pattern linePattern() {
    Pattern lp = linePattern;
    if (lp == null)
        linePattern = lp = Pattern.compile(LINE_PATTERN);
    return lp;
}

Why it was done in such a complex way and not just, say,

private static Pattern linePattern() {
    if (linePattern == null)
        linePattern = Pattern.compile(LINE_PATTERN);
    return linePattern;
}

What was the point of using a local variable (lp) here? Is this some kind of optimization technique? Or maybe precaution against a concurrent modification? But linePattern cannot be set to null again as this method is the only place where it is modified.

like image 279
John McClane Avatar asked Nov 20 '18 23:11

John McClane


2 Answers

I believe it's all about volatile keyword used with linePattern and separatorPattern fields

private static volatile Pattern separatorPattern;
private static volatile Pattern linePattern;

Due to specification

The Java programming language allows threads to access shared variables (§17.1). As a rule, to ensure that shared variables are consistently and reliably updated, a thread should ensure that it has exclusive use of such variables by obtaining a lock that, conventionally, enforces mutual exclusion for those shared variables.

The Java programming language provides a second mechanism, volatile fields, that is more convenient than locking for some purposes.

A field may be declared volatile, in which case the Java Memory Model ensures that all threads see a consistent value for the variable

Also here we can read that

Declaring a volatile Java variable means:

  • The value of this variable will never be cached thread-locally: all reads and writes will go straight to "main memory";

  • Access to the variable acts as though it is enclosed in a synchronized block, synchronized on itself.

That's why they firstly try to directly read field's value and then overwrite it with new value

like image 71
m.antkowicz Avatar answered Sep 28 '22 19:09

m.antkowicz


I think they want to avoid reading from the volatile field twice.

  • once to check if it is null
  • once for the return

In their version, they only

  • once read it into a local variable
  • confirm that the local variable is not null
  • return that local variable

The optimization is for the "happy path" that happens incredibly more often than the "initial setup path" that only happens once -- or at most a few times if the method is called concurrently before it has finished the initialization.

Or maybe precaution against a concurrent modification?

There is no such precaution. If you call linePattern concurrently before it has finished setting the static volatile field, the pattern will be created multiple times (but that's fine), different instances will be returned and a random one of those will be chosen to stick around (that's also fine, since they are equivalent).

Any measures to guard against that would only add cost to our "happy path" so should only be done in situations were the instance must really be a singleton for some reason.

like image 27
Thilo Avatar answered Sep 28 '22 18:09

Thilo