How can an immutable object, with non-final fields, be thread unsafe?

后端 未结 4 598
栀梦
栀梦 2021-01-02 13:06

say we have this

// This is trivially immutable.
public class Foo {
    private String bar;
    public Foo(String bar) {
        this.bar = bar;
    }
    pu         


        
相关标签:
4条回答
  • 2021-01-02 13:06

    From the link posted in comments:

    class FinalFieldExample { 
        final int x;
        int y; 
        static FinalFieldExample f;
    
        public FinalFieldExample() {
            x = 3; 
            y = 4; 
        } 
    
        static void writer() {
            f = new FinalFieldExample();
        } 
    
        static void reader() {
            if (f != null) {
                int i = f.x;  // guaranteed to see 3  
                int j = f.y;  // could see 0
            } 
        } 
    }
    

    One thread may call writer() and and another thread may call reader(). The if condition in reader() could evaluate to true, but becuase y is not final the object initalizion may not have completely finished (so the object has not been safely published yet), and thus int j = 0 could happen as it has not been initialized.

    0 讨论(0)
  • 2021-01-02 13:10

    Most probably you've got your answer by now, but just to make sure I wanted to add my explanation also.

    In order for an object (for your case) to be thread-safe, it must:

    • Be immutable
    • Be safely published

    Immutable - you made it so. There is no way to modify bar once it has been set. Pretty obvious here.

    Safely published. As per the example, the code is not safely published. Because bar is not final the compiler is free to reorder it as it finds appropriate. The compiler could publish(write to main memory) the reference to Foo instance, before the write to bar. That would mean that bar is null. So, first the reference to Foo is written to main memory, then the write to bar happens. In between these two events another thread can see the stale bar as being null.

    If you add final to it, the JMM will guarantee that:

    the values of final fields are guaranteed to be visible to other threads accessing the constructed object.

    Or, final field prevents reordering. Thus making that variable final will ensure thread safety.

    0 讨论(0)
  • 2021-01-02 13:14

    Due to JVM optimization, you can never assume that operations are executed in the order they are written, unless it matters for the same thread. So when you call the constructor and then pass a reference to the resulting object to another thread, the JVM might not actually write the value of foo.bar before it is needed within the same thread.

    That means that in a multithreaded environment, the getBar method could be called before the value in the constructor was written to it.

    0 讨论(0)
  • 2021-01-02 13:23

    Foo is thread safe once it has been safely published. For example, this program could print "unsafe" (it probably won't using a combination of hotspot/x86) - if you make bar final it can't happen:

    public class UnsafePublication {
    
        static Foo foo;
    
        public static void main(String[] args) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (foo == null) {}
                    if (!"abc".equals(foo.getBar())) System.out.println("unsafe");
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    foo = new Foo("abc");
                }
            }).start();
        }
    }
    
    0 讨论(0)
提交回复
热议问题