关于Java中扩展线程安全类的问题

新手上路,请多包涵

最近在读《Java并发编程实战》,里面的4.4.1节,有个例子:假设我们需要一个线程安全的List,它需要提供给我们一个原子的“缺少即加入(put-if-absent)”操作。并提供了2种实现:

  1. 非线程安全的“缺少即加入”实现
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;
    }
}
  1. 使用客户端加锁实现的“缺少即加入”
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;
        }
    }
}

感觉书中的翻译比较晦涩,一直没理解这里为什么第一个是非线程安全的而第二个就是线程安全的,请大神看看是怎么回事

阅读 3.1k
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()等操作,进而造成线程不安全

Collections.synchronizedList(new ArrayList<E>());方法内部锁住的对象是SynchronizedRandomAccessList

图片描述

图片描述

方法一synchronized修饰方法锁住的是当前对象
方法二list返回的就是SynchronizedRandomAccessList和方法内部的mutex一致

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题