Multithreading 对Java中的安全发布和可见性感到困惑,尤其是对不可变对象
当我阅读Brian Goetz的《Java并发性实践》时,我记得他在关于可见性的一章中说过:“另一方面,即使不使用同步来发布对象引用,也可以安全地访问不可变对象” 我认为这意味着,如果发布一个不可变的对象,所有字段(包括可变的最终引用)对可能使用它们的其他线程都是可见的,并且至少是到该对象完成构造时为止的最新字段 现在,我读到了 说到这里,如果线程构造了一个不可变的对象(也就是只包含final字段的对象),那么,如果要确保其他所有线程都能正确地看到它,通常仍需要使用同步。没有其他方法可以确保第二个线程能看到对不可变对象的引用。程序从最终字段获得的保证应经过仔细的处理,并加上深入细致的处理l了解如何在代码中管理并发性。” 他们似乎互相矛盾,我不知道该相信哪一个 我还读到,如果所有字段都是final,那么即使对象不是不可变的,我们也可以确保安全发布。 例如,由于这种保证,我一直认为Brian Goetz的并发性实践中的这段代码在发布此类对象时是很好的Multithreading 对Java中的安全发布和可见性感到困惑,尤其是对不可变对象,multithreading,concurrency,thread-safety,visibility,immutability,Multithreading,Concurrency,Thread Safety,Visibility,Immutability,当我阅读Brian Goetz的《Java并发性实践》时,我记得他在关于可见性的一章中说过:“另一方面,即使不使用同步来发布对象引用,也可以安全地访问不可变对象” 我认为这意味着,如果发布一个不可变的对象,所有字段(包括可变的最终引用)对可能使用它们的其他线程都是可见的,并且至少是到该对象完成构造时为止的最新字段 现在,我读到了 说到这里,如果线程构造了一个不可变的对象(也就是只包含final字段的对象),那么,如果要确保其他所有线程都能正确地看到它,通常仍需要使用同步。没有其他方法可以确保第二
@ThreadSafe
public class MonitorVehicleTracker {
@GuardedBy("this")
private final Map<String, MutablePoint> locations;
public MonitorVehicleTracker(
Map<String, MutablePoint> locations) {
this.locations = deepCopy(locations);
}
public synchronized Map<String, MutablePoint> getLocations() {
return deepCopy(locations);
}
public synchronized MutablePoint getLocation(String id) {
MutablePoint loc = locations.get(id);
return loc == null ? null : new MutablePoint(loc);
}
public synchronized void setLocation(String id, int x, int y) {
MutablePoint loc = locations.get(id);
if (loc == null)
throw new IllegalArgumentException("No such ID: " + id);
loc.x = x;
loc.y = y;
}
private static Map<String, MutablePoint> deepCopy(
Map<String, MutablePoint> m) {
Map<String, MutablePoint> result =
new HashMap<String, MutablePoint>();
for (String id : m.keySet())
result.put(id, new MutablePoint(m.get(id)));
return Collections.unmodifiableMap(result);
}
}
public class MutablePoint { /* Listing 4.5 */ }
@ThreadSafe
公共类监视器车辆追踪器{
@担保人(“本”)
私人最终地图位置;
公共监视器(
(地图位置){
this.locations=deepCopy(位置);
}
公共同步映射getLocations(){
返回副本(位置);
}
公共同步可变点getLocation(字符串id){
可变点位置=位置。获取(id);
返回loc==null?null:新可变点(loc);
}
公共同步的void setLocation(字符串id,int x,int y){
可变点位置=位置。获取(id);
如果(loc==null)
抛出新的IllegalArgumentException(“无此类ID:+ID”);
loc.x=x;
位置y=y;
}
私有静态映射副本(
地图(m){
映射结果=
新的HashMap();
对于(字符串id:m.keySet())
结果.put(id,新可变点(m.get(id));
返回集合。不可修改映射(结果);
}
}
公共类可变点{/*清单4.5*/}
例如,在这个代码示例中,如果最终保证为false,并且某个线程创建了该类的实例,那么对该对象的引用不为null,但另一个线程使用该类时字段位置为null,该怎么办
再一次,我不知道哪一个是正确的,或者我是否碰巧误解了这篇文章或戈茨这个问题以前已经被回答过好几次了,但我觉得这些答案中有很多是不充分的。见:
- 等等
final
字段将保持其预期值(请注意初始化和发布之间的差异)。代码示例还同步对位置
字段的访问,以确保对最终
字段的更新是线程安全的
事实上,为了进一步说明,我建议您查看JCIP清单3.13(VolatileCachedFactorizer
)。请注意,尽管OneValueCache
是不可变的,但它存储在volatile
字段中。为了说明常见问题解答语句,VolatileCachedFactorizer
如果没有volatile
,将无法正常工作。“同步”是指使用volatile
字段,以确保对其进行的更新对其他线程可见
说明第一条JCIP语句的一个好方法是删除volatile
。在这种情况下,cachedFactoryer
将无法工作。考虑一下:如果一个线程设置了一个新的缓存值,但另一个线程试图读取该值,字段不是“代码>易失性< /代码>?读卡器可能看不到更新的OneValueCache
。但是,回想一下Goetz引用了不可变对象的状态,如果读线程碰巧看到存储在缓存
中的OneValueCache
的最新实例,那么该实例的状态将是可见的,并且构造正确
因此,尽管有可能丢失对缓存的更新
,但是不可能丢失OneValueCache
的状态(如果已读取),因为它是不可变的。我建议读t