Search code examples
javajava-memory-model

Data race between static initializer and constructor of different classes


Per the JMM, section 9.2.3

The rules for class initialization ensure that any thread that reads a static field will be synchronized with the static initialization of that class.

I am trying to understand what happens when a class (b) attempts to read a static field from class (a), and (a) is still being initialized. Is there any guarantee that (b) will read the correct value of (a) (i.e. after static initialization)?

As an example, is there a data race on field_long?

class TextField {
    public static final FieldType TEXT_FIELD_TYPE = new FieldType();

    static {
        TEXT_FIELD_TYPE.set_field_long(1L);
    };
}

class FieldType {
    public Long field_long;

    public void set_field_long(Long l) { field_long = l; }
    public Long get_field_long() { return field_long; }

    public FieldType(){ set_field_long(-1L); }
    public FieldType(FieldType ref) { this.field_long = ref.get_field_long(); }
}

public class Main {
    public static void main(String... args) {
        Thread t1 = new Thread(() -> {
            FieldType f = new FieldType(TextField.TEXT_FIELD_TYPE);
        });

        Thread t2 = new Thread(() -> {
            FieldType f = new FieldType(TextField.TEXT_FIELD_TYPE);
        });

       t1.start();
       t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Solution

  • The race condition does not occur because the JVM synchronizes the loading and initialization of classes using initialization locking mechanism, as specified in the Java Virtual Machine Specification. Unfortunately, during synchronization, the threads do not change their state to BLOCKED and are still in the RUNNABLE state, but the presence of synchronization can be confirmed using a simple example of a deadlock:

    public class DeadlockDemo {
    
        static class A {
            static final B b = new B();
        }
    
        static class B {
            static final A a = new A();
        }
    
        public static void main(String[] args) {
            new Thread(A::new).start();
            new B();
        }
    }