如果我像这样修改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
}
}
使用如下:
addParam
来准备初始化(唯一的写线程)getInstance
来获取单例实例(许多读取线程)getInstance
的初始调用可以通过编写线程或其中一个读取线程来执行这种使用方法是否安全(虽然令人费解)?
根据哪个主题(读者或作者)首先调用getInstance
?,是否有任何变化?
如果制作静态initParams
变量volatile
,是否会发生任何变化?
如果第一次调用是由读取线程(在编写线程之前创建)执行的,那么initParams
的内部视图是否会过时并导致初始化的实例也过时了?
答案 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的写入,因为与类加载不同步,因此可以读取陈旧。