Java 使用双重检查习惯用法纠正错误 公共类生成器{ 私有静态最终映射缓存=新HashMap(); 公共静态字节[]生成(字节[]src){ byte[]generated=cache.get(src); if(生成==null){ 已同步(缓存){ generated=cache.get(src); if(生成==null){ 生成=数据生成(src); cache.put(src,已生成); } } } 产生的回报; } 私有静态字节[]数据生成(字节[]src){…}
} 有人能回答吗,这个密码出了什么问题?Java 使用双重检查习惯用法纠正错误 公共类生成器{ 私有静态最终映射缓存=新HashMap(); 公共静态字节[]生成(字节[]src){ byte[]generated=cache.get(src); if(生成==null){ 已同步(缓存){ generated=cache.get(src); if(生成==null){ 生成=数据生成(src); cache.put(src,已生成); } } } 产生的回报; } 私有静态字节[]数据生成(字节[]src){…},java,multithreading,Java,Multithreading,} 有人能回答吗,这个密码出了什么问题? 也许generate()方法可以返回部分构造的数组,不是吗?首先,您使用的是未同步的哈希映射。比如说,这可能导致无限循环(这是真实的——在生产环境中已经观察到) 它还有一个常见的双重检查锁定错误,即从写入数组内容到读取数组(我假设会有一些数据)之间没有“发生在”关系 (注意,此处比较的是对象标识,而不是数组内容。) 另一个问题是,您有可变静态,这是一种糟糕的设计,会导致严重的问题。当一个线程正在执行第11行时,另一个线程可能正在执行第5行。这就是为什么在
也许generate()方法可以返回部分构造的数组,不是吗?首先,您使用的是未同步的
哈希映射。比如说,这可能导致无限循环(这是真实的——在生产环境中已经观察到)
它还有一个常见的双重检查锁定错误,即从写入数组内容到读取数组(我假设会有一些数据)之间没有“发生在”关系
(注意,此处比较的是对象标识,而不是数组内容。)
另一个问题是,您有可变静态,这是一种糟糕的设计,会导致严重的问题。当一个线程正在执行第11行时,另一个线程可能正在执行第5行。这就是为什么在这里需要同步映射,以及为什么在使用非同步哈希映射时,结果将是不确定的
这里的解决方案是一个同步映射(参见java.util.Collections#synchronizedMap)。当然,如果使用完全同步的映射,则双重检查锁定(额外优化)将变得多余。用ConcurrentHashMap替换HashMap
您的同步看起来没有用。我会这样改写整个故事:
public class Generator {
private static final Map<byte[], byte[]> cache = new HashMap<byte[], byte[]>();
public static byte[] generate(byte[] src) {
byte[] generated = cache.get(src);
if (generated == null) {
synchronized (cache) {
generated = cache.get(src);
if (generated == null) {
generated = doGenerate(src);
cache.put(src, generated);
}
}
}
return generated;
}
private static byte[] doGenerate(byte[] src) {...}
private静态最终映射缓存=新的ConcurrentHashMap();
公共静态字节[]生成(字节[]src,int计数器){
byte[]generated=cache.get(src);
if(生成==null){
生成=数据生成(src);
cache.put(src,已生成);
}
产生的回报;
}
编辑:try/catch很奇怪,没有必要。出于OP的目的,使用ConcurrentHashMap就足够了。如果OP真的不想为同一数据集两次调用doGenerate而付出代价,那么他们将不得不继续使用DCL模式,但仍然建议使用ConcurrentHashMap。但在HashMap中,我只使用get()方法,而不使用synchronize。我可以从HashMap中获取不正确的数据吗?(put()放在同步块中,任何更改都必须在同步块完成后在主内存中获取)@artem,如果put()在get()使用这些结构时更改了映射的内部结构,则get()可能会像Tom所描述的那样失败。(所有地图的使用都必须同步。)你能告诉我更多,会出现什么问题吗。我不明白。我认为在最坏的情况下HashMap返回null…我不认为这个例子有“常规”的双重检查错误,因为synchronize
建立了一个before-before关系(根据这个:)@Arian-快速情况不会进入synchronized
块。因此,以前没有发生过。你能详细告诉我会出什么问题吗。我认为在最坏的情况下,generate()可以返回null。。。或者它可以返回部分构造的数组或日期不一致的数组,不是吗?副作用是特定于实现的-契约在这里很重要,该契约要求在多个线程访问HashMap,并且至少一个线程更改内容的情况下,访问必须同步。ConcurrentModificationException将是完全有效的结果。API文档中写道:“如果多个线程同时访问一个哈希映射,并且至少有一个线程在结构上修改了该映射,那么它必须在外部进行同步。”在该特定代码中,主要关注的不是生成值的潜在开销。最终可能会为同一个键获得不同的值。(putIfAbsent
在这里很有用。当然不要使用断开的双重检查锁定。)原始答案/代码具有误导性。ConcurrentHashMap必须与putIfAbsent()一起使用,如@TomHawtin tackline所述
private static final Map<byte[], byte[]> cache = new ConcurrentHashMap<byte[], byte[]>();
public static byte[] generate(byte[] src, int counter) {
byte[] generated = cache.get(src);
if (generated == null) {
generated = doGenerate(src);
cache.put(src, generated);
}
return generated;
}