关于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;
        }
    }
}

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


青春有我
浏览 788回答 2
2回答

芜湖不芜

首先要明白,synchronize加锁的一般都是对某个对象而言的(也可以对类进行加锁)。1中非线程安全的synchronized的加锁对象其实是ListHelper<E>实例化的对象,而不是list,其他的线程无法再对该ListHelper<E>实例化的对象进行操作,对于该例,就是无法再进行putIfAbsent()方法的使用,但是其中的list是public的,所以可以直接对list进行操作,比如list.add()等操作,进而造成线程不安全

慕尼黑的夜晚无繁华

大概是由于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)方法内的
打开App,查看更多内容
随时随地看视频慕课网APP