最近在读《Java并发编程实战》,里面的4.4.1节,有个例子:假设我们需要一个线程安全的List,它需要提供给我们一个原子的“缺少即加入(put-if-absent)”操作。并提供了2种实现:
- 非线程安全的“缺少即加入”实现
public class ListHelper<E> {
public List<E> list = Collections.synchronizedList(new ArrayList<E>());
public synchronized boolean putIfAbsent(E x) {
boolean absent = !list.contains(x);
if (absent) {
list.add(x);
}
return absent;
}
}
- 使用客户端加锁实现的“缺少即加入”
public class ListHelper<E> {
public List<E> list = Collections.synchronizedList(new ArrayList<E>());
public boolean putIfAbsent(E x) {
synchronized (list) {
boolean absent = !list.contains(x);
if (absent) {
list.add(x);
}
return absent;
}
}
}
感觉书中的翻译比较晦涩,一直没理解这里为什么第一个是非线程安全的而第二个就是线程安全的,请大神看看是怎么回事
大概是由于
public List<E> list = Collections.synchronizedList(new ArrayList<E>());
的这个list是public的,可以被直接修改的。假如有多个线程直接修改这个list。
方法1虽然把
putIfAbsent(E x)
这个方法加锁了,但是其他线程依然可以修改由于public暴露出来的list。比如线程A:
listHelper.list.add(x)
的时候,线程B刚好在执行boolean absent = !list.contains(x);
,有可能线程A把x添加进去了,但是线程B的absent也判断为true,然后线程B就再次add了x。方法2是根据list加锁,所以只有持有list的锁才能进入判断和添加,当线程A在
listHelper.list.add(x)
的时候,线程B是不能进入到synchronized (list)
方法内的