该实现有一些非常微妙的警告。
你似乎知道,但我要说清楚,
null
实例并输入
if
块
不必要地创建新的
DataWrapper
实例:
public static DataWrapper getInstance(String name) {
DataWrapper instance = map.get(name);
if (instance == null) {
instance = new DataWrapper(name);
}
return instance.cloneInstance();
}
看来你没问题,
loadData(name)
(使用人
DataWrapper(String)
)将始终返回相同的值。
无法保证加载数据的最后一个线程会将其存储在
map
,因此该值可能已过时。
如果你说这不会发生或者这不重要,
这很好,但这个假设至少应该记录在案。
为了演示另一个微妙的问题,让我将
instance.cloneInstance()
方法:
public static DataWrapper getInstance(String name) {
DataWrapper instance = map.get(name);
if (instance == null) {
instance = new DataWrapper(name);
}
return new DataWrapper(instance);
}
这里的微妙问题是,这个返回语句不是安全的发布。
新的
数据包装器
实例可以部分构造,
线程可能会在不一致的状态下观察到它,
例如,对象的字段可能尚未设置。
有一个简单的修复方法:
如果你成功了
name
data
领域
final
,
类变得不可变。
不可变类具有特殊的初始化保证,
return new DataWrapper(this);
成为安全出版物。
通过这个简单的改变,假设你对第一点没意见(
loadData
不具有时间敏感性),我认为实现应该正常工作。
我建议进行一项与正确性无关的额外改进,但与其他良好做法无关。
这是一个包裹
Data
,同时也是一个缓存。
额外的责任让人读起来有点困惑。
另外,并发哈希映射并没有真正发挥其潜力。
如果将责任分开,结果会更简单、更好、更容易阅读:
class DataWrapperCache {
private static final ConcurrentMap<String, DataWrapper> map = new ConcurrentHashMap<>();
public static DataWrapper get(String name) {
return map.computeIfAbsent(name, DataWrapper::new).defensiveCopy();
}
}
class DataWrapper {
private final String name;
private final Data data;
DataWrapper(String name) {
this.name = name;
this.data = loadData(name); // A heavy method
}
private DataWrapper(DataWrapper that) {
this.name = that.name;
this.data = that.data.cloneInstance();
}
public DataWrapper defensiveCopy() {
return new DataWrapper(this);
}
}