对Collections.synchronizedList的部分思考

首先来看两个例子。

线程不安全的:

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

线程安全的:

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方法是很正常的,可以理解的。但是假设ListHelper 类中有一个其他的方法对list进行修改,且该方法并未被sychronized锁住,那么线程B是可以进入这个方法的,那么很明显,list没有可见性的,从而就会导致不正确性。此时这个sychronized是没起到保证list正确性的,因此是线程不安全的。

也可以理解成因为A锁住的是外部锁,也就是调用putIfAbsent方法的对象,其实对list是没有锁住的,因此list还是可以修改。即使list是synchronizedList,是线程安全的,但内外部锁不一致还是会导致线程不安全。

再考虑一种情况,假设list没有被Collections.synchronizedList修饰,还是原来的ArrayList,那么即使线程安全的那一版也是线程不安全的,因为锁住的只是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,与内部锁mutex 一致,其他的方法即使没有锁修饰,但是想改动list也会发现它已经被锁住,而无法修改,因此是线程安全的。

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值