如果参数化,按需初始化持有者的习惯用法是否仍然安全?

时间:2018-03-29 13:55:16

标签: java multithreading concurrency thread-safety singleton

如果我像这样修改the stock initialization-on-demand holder idiom example

public class Something {

    //internal params, unchangeable after the initialization
    private final List<String> params;

    //changeable params that will be used for initialization
    private final static List<String> initParams = new ArrayList<>();

    private Something(List<String> params) {
        this.params = params;
    }

    //allow params to be added prior to initialization
    public static void addParam(String param) {
        initParams.add(param);
    }

    private static class LazyHolder {
        static final Something INSTANCE =
            new Something(Collections.unmodifiableList(initParams));
    }

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

    public int calculate() {
       return params.size(); //use the internal params somehow
    }
}

使用如下:

  • 只有1个线程多次调用addParam来准备初始化(唯一的线程)
  • 许多线程(可能是在编写线程之前创建的)稍后调用getInstance来获取单例实例(许多读取线程)
  • getInstance的初始调用可以通过编写线程或其中一个读取线程来执行

这种使用方法是否安全(虽然令人费解)? 根据哪个主题(读者或作者)首先调用getInstance,是否有任何变化? 如果制作静态initParams变量volatile,是否会发生任何变化?

如果第一次调用是由读取线程(在编写线程之前创建)执行的,那么initParams的内部视图是否会过时并导致初始化的实例也过时了?

2 个答案:

答案 0 :(得分:1)

  

这种用法是否安全(尽管有问题)?

不,我不认为您在目前所述的问题中有任何保证,在getInstance的最后一次通话之后,第一次拨打addParam即会发生。

你确实说第一次调用getInstance将会发生&#34;稍后&#34;但是你可以通过说&#34; [读者主题]可能已经创建了写线程&#34;,这让我觉得你不能保证这个订单。

如果你可以保证这个&#34;发生在&#34;之后init和读取之间的关系,然后我认为这是线程安全的,是的,原因与更简单的版本相同。

  

根据哪个线程(读者或编写者)首次调用getInstance?

,是否会发生任何变化

是的,如果编写者线程是第一个调用getInstance的,那么事情应该是线程安全的。这是因为它对全局变量initParams的写入保证会发生 - 之后再读取同一个线程。

然而,这基本上与我上面讨论过的保证相同,所以我认为这只是为了解决问题。

  

如果静态initParams变量变为volatile,有什么改变吗?

这将有助于缩小第一次拨打getInstance和最后一次拨打addParam之间的比赛,但我认为你仍然依赖后者发生的事情&#34;&#34;&#34;前者。

退一步

这种设计很脆弱,复杂而且丑陋。您正在使用全局变量&#34; initParams&#34;并依赖一些非常棘手的副作用和排序。

你说你有一个&#34;作家线程&#34;它构建了&#34; Something&#34;实例和那个&#34;之后&#34;它会被阅读。

你能否重新安排一些事情,以便所有的写作都在读取开始之前严格发生,并完全停止使用静态变量?

答案 1 :(得分:0)

您的示例不是线程安全的。您在订购之前就丢失了这一事件,因为您引入了一个共享变量的写入,而该共享变量没有受到线程安全类加载的保护。

场景将是

Thread 1: Write Thread
   read initParams
   initParams.add(param)

Thread 2: First read thread
   synchronized(class_loader_lock){ 
      load class
      read initParams
   }

线程1的写入,因为与类加载不同步,因此可以读取陈旧。