【集合类型的并发】Collections.synchronizedList

摘要: 详细的解析:Collections.synchronizedList
    1 :关注要点,为何在有synchroniezed方法的同时会出现 Collections.synchronizedList
    2 :知识背景: 您可能须要了解java Synchronized方法的加锁的各类机制,包括如何上锁,锁对象
    3 : plus: 您须要不断的深化 Java加锁的各类机制

@NotThreadSafe  
class BadListHelper <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;  
    }  
}  
 
  这个示例但愿实现的功能是为List提供一个原子操做:若没有则添加。由于ArrayList自己不是线程安全的,因此经过集合Collections.synchronizedList将其转换为一个线程安全的类,而后经过一个辅助的方法来为List实现这么个功能。初看起来这个方法没问题,由于也添加了synchronized关键字实现加锁了。
 
 可是仔细分析,你会发现问题。首先对于synchronized关键字,须要说明的是,它是基于当前的对象来加锁的,上面的方法也能够这样写:

public boolean putIfAbsent(E x) {  
    synchronized(this) {  
        boolean absent = !list.contains(x);  
        if (absent)  
            list.add(x);  
        return absent;  
    }  
}  

  因此这里的锁实际上是BadListHelper对象, 而能够确定的是Collections.synchronizedList返回的线程安全的List内部使用的锁绝对不是BadListHelper的对象,应为你在声明和初始化这个集合的过程之中,你尚且都不知道这个对象的存在。因此BadListHelper中的putIfAbsent方法和线程安全的List使用的不是同一个锁,所以上面的这个加了synchronized关键字的方法依然不能实现线程安全性。
 
 下面给出书中的另外一种正确的实现:

@ThreadSafe  
class GoodListHelper <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;  
        }  
    }  
}  
  若是你要分析这个实现是否正确,你须要搞清楚Collections.synchronizedList返回的线程安全的List内部使用的锁是哪一个对象,因此你得看看Collections.synchronizedList这个方法的源码了。该方法源码以下:
public static <T> List<T> synchronizedList(List<T> list) { return (list instanceof RandomAccess ? new SynchronizedRandomAccessList<T>(list) : new SynchronizedList<T>(list)); } 经过源码,咱们还须要知道ArrayList是否实现了RandomAccess接口: public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable 查看ArrayList的源码,能够看到它实现了RandomAccess,因此上面的synchronizedList放回的应该是SynchronizedRandomAccessList的实例。接下来看看SynchronizedRandomAccessList这个类的实现: static class SynchronizedRandomAccessList<E> extends SynchronizedList<E> implements RandomAccess { SynchronizedRandomAccessList(List<E> list) { super(list); } SynchronizedRandomAccessList(List<E> list, Object mutex) { super(list, mutex); } public List<E> subList(int fromIndex, int toIndex) { synchronized(mutex) { return new SynchronizedRandomAccessList<E>( list.subList(fromIndex, toIndex), mutex); } } static final long serialVersionUID = 1530674583602358482L; private Object writeReplace() { return new SynchronizedList<E>(list); } } 由于SynchronizedRandomAccessList这个类继承自SynchronizedList,而大部分方法都在SynchronizedList中实现了,因此源码中只包含了不多的方法,可是经过subList方法,咱们能够看到这里使用的锁对象为mutex对象,而mutex是在SynchronizedCollection类中定义的,因此再看看SynchronizedCollection这个类中关于mutex的定义部分源码: Java代码 收藏代码 static class SynchronizedCollection<E> implements Collection<E>, Serializable { private static final long serialVersionUID = 3053995032091335093L; final Collection<E> c; // Backing Collection final Object mutex; // Object on which to synchronize SynchronizedCollection(Collection<E> c) { if (c==null) throw new NullPointerException(); this.c = c; mutex = this; } SynchronizedCollection(Collection<E> c, Object mutex) { this.c = c; this.mutex = mutex; } } 能够看到mutex就是当前的SynchronizedCollection对象,而SynchronizedRandomAccessList继承自SynchronizedList,SynchronizedList又继承自SynchronizedCollection,因此SynchronizedRandomAccessList中的mutex也就是SynchronizedRandomAccessList的this对象。因此在GoodListHelper中使用的锁list对象,和SynchronizedRandomAccessList内部的锁是一致的,因此它能够实现线程安全性。
class BadListHelper <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; } } 

putIfAbsent方法和List并非使用的同一个锁对象,List使用的锁对象并非BadListHelper,而是list。假如A线程进入putIfAbsent方法,list这个锁并无被获取(A线程获取的是 BadListHelper这个对象),因此其余线程还可以得到list锁对象来改变list对象。boolean absent = !list.contains(x);当线程到这串代码结束时,其余线程得到list锁对象,从而就能调用list的方法来改变list对象,这时候就可能致使!list.contains(x)改变,即域absent并非A线程获得的布尔类型。因此这个类并非线程安全的。java

得到变量的锁就能够改变变量,没有得到变量锁的就不能改变,得到方法的锁就能够执行方法里面的语句。安全

相关文章
相关标签/搜索