Java synchronized无法阻止修改类成员内部元素的线程安全疑问
JCP 4.4.2 节ImprovedList线程安全问题解答
你的判断根本没出错,书里给这个类标@ThreadSafe是带明确使用前提的,不是无条件线程安全。
原示例代码
@ThreadSafe public class ImprovedList<T> implements List<T>{ private final List<T> list; public ImprovedList(List<T> list){this.list=list;} public synchronized boolean putIfAbsent(T x){ boolean contains = list.contains(x); if(contains){ list.add(x); } return !contains; } @Override public synchronized void clear(){list.clear();} // 其余List接口的实现方法都需要按相同规则加synchronized锁 }
顺便提一句,这段示例里
putIfAbsent的判断条件写反了:只有元素不存在的时候才需要add,正确的判断应该是if(!contains),属于印刷级的小笔误。
问题核心解释
- 这个类的线程安全逻辑,本质是靠ImprovedList实例这一把内置锁,把所有对底层list的访问都串行化。这个逻辑能成立的唯一前提是:当你把list传入ImprovedList的构造方法之后,外部就再也不能直接持有、操作这个原始list的引用,所有和这个列表相关的操作都必须走ImprovedList的实例方法。
- 你担心的「锁管不到list内部元素、外部线程直接改list会导致putIfAbsent结果不准」的情况是100%会发生的。这属于典型的对象逸出问题:如果类内部持有的状态引用漏到了封装边界外面,不管你内部加多少锁,都挡不住外面的代码直接改状态。
putIfAbsent本身是“先检查再执行”的复合操作,一旦有线程能绕过锁碰到底层list,检查元素是否存在、执行添加两个步骤之间就会出现竞态,最后出现“方法返回说元素不存在,实际上元素已经被外部加进去了”的不一致问题。 - 书里放这段代码的目的,是演示「用组合+统一锁,给非线程安全容器加原子操作」的实现思路,默认看这部分内容的读者已经知道“不能把封装的内部引用泄露出去”这个线程安全基本规则。真要在生产环境用这种写法,一般都会在构造方法里做个防御性拷贝,从根上堵死外部直接改内部list的可能:
加了这层拷贝之后,这个类才是不需要额外约定、自包含的线程安全实现。public ImprovedList(List<T> list){ // 复制入参生成内部专属的list实例,外面持有的原始引用怎么改都影响不到内部 this.list = new ArrayList<>(list); }
内容的提问来源于stack exchange,提问作者Troskyvs
相关产品推荐
相关产品推荐

