对Collections.synchronizedList的部分思考

重温《Java并发编程实战》中,有地方引起了我的注意,以前估计读的也是一知半解的略过了,但是现在对多线程有着不一样的体悟之后,经过一段苦思冥想之后,终于想通了。

这边把代码贴出来。

线程不安全的:

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

线程安全的:

@ThreadSafe  
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;  
        }  
    }  
}

一开始很诧异为啥上面的线程不安全,下面的线程安全。
不过想通了就很好解释了,下面是我的思考:

这个方法的作用是,如果没有则添加,所以就必须保证list 这个变量的正确性。

假设有一个线程A,一个线程B,都有同一个ListHelper 对象,锁住了putIfAbsent方法,乍一看是没什么问题的,但是仔细想想。sychronized获取到的只是这个ListHelper 对象的监视锁,假设线程A获取到了这个锁,那么线程B不能进入putIfAbsent方法是很正常的,可以理解的,但是假设这类中有一个其他的方法对list进行修改,且该方法并未被sychronized锁住,线程B是可以进入这个方法的,那么很明显,list没有可见性的,从而就会导致不正确性。那么这个sychronized是没起到保证list正确性的,因此是线程不安全的。

还有一个情况是,假设该list并没有被Collections.synchronizedList修饰的话,锁住的也只是list对象,也就是说,还是和上面差不多,因为ArrayList是线程不安全的,里面的方法并没有被sychronized修饰,这样的话就会导致还是可以在其他方法中修改该list的。

但是使用了Collections.synchronizedList就不一样了。下面看一下源码:

	public static <T> List<T> synchronizedList(List<T> list) {
        return (list instanceof RandomAccess ?
                new SynchronizedRandomAccessList<>(list) :
                new SynchronizedList<>(list));
    }

可以看到,实现了RandomAccess 接口的会返回一个SynchronizedRandomAccessList类,否则返回SynchronizedList类。由于ArrayList是实现了这个接口,因此看一下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<>(
                    list.subList(fromIndex, toIndex), mutex);
            }
        }

        private static final long serialVersionUID = 1530674583602358482L;

        
        private Object writeReplace() {
            return new SynchronizedList<>(list);
        }
    }

会发现这个类继承自SynchronizedList,然后subList方法中使用mutex监视器锁(其实每个方法都会锁住mutex,只是这里没贴出来),但是这个mutex并没有在其中定义,这时来看看构造方法:

	 SynchronizedRandomAccessList(List<E> list, Object mutex) {
            super(list, mutex);
        }

这里有个mutex变量,点进去看,会发现该变量定义在SynchronizedCollection中,那么再来看一下SynchronizedCollection的部分代码:

	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) {
            this.c = Objects.requireNonNull(c);
            mutex = this;
        }
    }

可以看到这里定义了变量mutex,同时这个mutex在构造方法中赋值为this。因此,在线程安全的ListHelper 中锁住的list,与内部锁一致,因此是线程安全的。

通过上面的源码,就可以知道线程安全的ListHelper 安全在哪了,使用之前对线程不安全的ListHelper 分析来线程安全的ListHelper ,会发现之前线程不安全的原因在这个类中不存在了。

以上,就是我对Collections.synchronizedList的部分思考,如有错误,欢迎指教。

猜你喜欢

转载自blog.csdn.net/RebelHero/article/details/91397344
今日推荐