Should I synchronize a static volatile variable?

无人久伴 提交于 2020-01-22 19:40:13

问题


There are a few questions on this subject, but most skirt around this issue because it's not the intent of the question.

If I have a static volatile in my class:

private static volatile MyObj obj = null;

and in a method below I do:

public MyObj getMyObj() {
    if (obj == null) {
        obj = new MyObj();// costly initialisation
    }
    return obj;
}

will I need to synchronize to ensure only one thread writes to the field, or will any writes be immediately visible to other threads evaluating the obj == null conditional?

To put it another way: does volatile get you around having to synchronize access to writes on a static variable?


回答1:


You'd definitely need some sort of locking to ensure that only one thread writes to the field. Regardless of the volatility, two threads can both "see" that obj is null, and then both start initializing with your current code.

Personally I'd take one of three options:

  • Initialize on class load (knowing that that will be lazy, but not as lazy as waiting until getMyObj is first called):

    private static final MyObj obj = new MyObj();
    
  • Use unconditional locking:

    private static MyObj obj;
    private static final Object objLock = new Object();
    
    public static MyObj getMyObj() {
        synchronized(objLock) {
            if (obj == null) {
                obj = new MyObj();
            }
            return obj;
        }
    }
    
  • Use a nested class for laziness that way:

    public static MyObj getMyObj() {
        return MyObjHolder.obj;
    }
    
    private static class MyObjHolder {
        static final MyObj obj = new MyObj();
    }
    



回答2:


Yes, you should absolutely synchronize (or use a better idiom like the Singleton Holder idiom). Otherwise you run the risk of multiple threads initializing your object multiple times (and then subsequently using different instances).

Consider a sequence of events like this:

  1. Thread A enters getMyObj() and sees that obj == null
  2. Thread B enters getMyObj() and sees that obj == null
  3. Thread A constructs a new MyObj() - let's call it objA
  4. Thread B constructs a new MyObj() - let's call it objB
  5. Thread A assigns objA to obj
  6. Thread B assigns objB to obj (which is not null anymore at this point, so the reference to objA, assigned by Thread A, is overwritten)
  7. Thread A exits getMyObj() and starts to use objA
  8. Thread B exits getMyObj() and starts to use objB

This scenario can happen with any number of threads. Note that although here, for the sake of simplicity, I assumed a strict ordering of events, in a real multithreaded environment events 1-2, 3-4 and/or 7-8 can partly or fully overlap in time, without changing the end result.

An example to the holder idiom:

public class Something {
    private Something() {
    }

    private static class LazyHolder {
        public static final Something INSTANCE = new Something();
    }

    public static Something getInstance() {
        return LazyHolder.INSTANCE;
    }
}

This is guaranteed to be safe, as INSTANCE is final. The Java memory model guarantees that final fields are initialized and made visible correctly to any number of threads, upon loading the containing class. Since LazyHolder is private and is referenced only by getInstance(), it will only get loaded when getInstance() is first called. And at that point, INSTANCE is initialized in the background and published safely by the JVM.




回答3:


No, you will still need to synchronize the access. volatile allows changes made to a variable by one thread to be seen by other threads.

Imagine the following flow of execution (assuming two threads T1 & T2):

  1. obj is null initally.
  2. T1: if (obj == null): YES
  3. T2: if (obj == null): YES
  4. T1: creates a new instance of MyObj and assign it to obj
  5. T2: also creates a new instance of MyObj and assign it to obj

You create twice an object you expected to be created only once. And this is not the worse scenario. You could end up returning an object that is no longer assigned to your variable too.




回答4:


That code is not thread safe. If multiple threads execute the function then multiple instances of MyObj could be created. You need some form of synchronization here.

The fundamental issue is that this block code:

if (obj == null) {
     obj = new MyObj();// costly initialisation
}

is not atomic. In fact it's a very long way from being atomic.




回答5:


Yet another way to handle this is double-checking (NOTE: works only with Java 5 or newer, see here for details):

public class DoubleCheckingSingletonExample
{
    private static final Object lockObj = new Object();

    private static volatile DoubleCheckingSingletonExample instance;

    private DoubleCheckingSingletonExample()
    {
        //Initialization
    }

    public static DoubleCheckingSingletonExample getInstance()
    {
        if(instance == null)
        {
            synchronized(lockObj)
            {
                if(instance == null)
                {
                    instance = new DoubleCheckingSingletonExample();
                }
            }
        }

        return instance;
    }
}

When getInstance is called from two threads simultaneously, both will first see the instance as null, another one enters the synchronized-block, and instantiates the object. The other will see instance is no longer null, and won't try to instantiate it. Further calls to getInstance will see that the instance is not null, and won't try to lock at all.



来源:https://stackoverflow.com/questions/10697451/should-i-synchronize-a-static-volatile-variable

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!