zoukankan      html  css  js  c++  java
  • 【集合类型的并发】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线程得到的布尔类型。所以这个类并不是线程安全的。

    获得变量的锁就可以改变变量,没有获得变量锁的就不能改变,获得方法的锁就可以执行方法里面的语句。

  • 相关阅读:
    POJ 1141 括号匹配 DP
    881. Boats to Save People
    870. Advantage Shuffle
    874. Walking Robot Simulation
    文件操作
    861. Score After Flipping Matrix
    860. Lemonade Change
    842. Split Array into Fibonacci Sequence
    765. Couples Holding Hands
    763. Partition Labels
  • 原文地址:https://www.cnblogs.com/yaowen/p/5983136.html
Copyright © 2011-2022 走看看