关于Java中扩展线程安全类的问题
最近在读《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;
}
}
}
感觉书中的翻译比较晦涩,一直没理解这里为什么第一个是非线程安全的而第二个就是线程安全的,请大神看看是怎么回事
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
大概是由于
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)
方法内的首先要明白,synchronize加锁的一般都是对某个对象而言的(也可以对类进行加锁)。
1中非线程安全的synchronized的加锁对象其实是ListHelper<E>实例化的对象,而不是list,其他的线程无法再对该ListHelper<E>实例化的对象进行操作,对于该例,就是无法再进行putIfAbsent()方法的使用,但是其中的list是public的,所以可以直接对list进行操作,比如list.add()等操作,进而造成线程不安全
方法一synchronized修饰方法锁住的是当前对象
方法二list返回的就是SynchronizedRandomAccessList和方法内部的mutex一致