SynchronizedList的同步问题
来源:互联网 发布:阿里云大学官网 编辑:程序博客网 时间:2024/06/13 19:09
ArrayList是非线程安全的,在多线程中同时操作ArrayList会经常出现ConcurrentModificationException。为了解决同步问题,java提供了Collections的同步类:SynchronizedList、SynchronizedMap、SynchronizedSet等。以SynchronizedList为例,从字面意义上看,应该是线程安全的。在我们的代码中,我们使用了SynchronizedList,初始代码如下:
List list = Collections.synchronizedList(new ArrayList());
在运用过程中,不对该List加锁处理。APP上线后,定时查看后台返回的crash信息,发现对该list的操作依然出现了ConcurrentModificationException。crash信息中显示该异常发生在执行for循环时:
for(Object object : list){ .... }
错误的最后一句执行代码为:java.util.ArrayList$Itr.next(ArrayList.java:831)
看来SynchronizedList并不像想象的那样绝对保证线程安全,那问题如何出现的呢?
先从增强for循环的实现说起。
增强for循环是基于迭代器实现的,如原始代码为:
for (Integer i : list) { System.out.println(i); }
反编译后,可以看到如下代码:
Integer i;for(Iterator iterator = list.iterator(); iterator.hasNext(); System.out.println(i)){ i = (Integer)iterator.next(); }
而我们的crash最后一句就是java.util.ArrayList$Itr.next(ArrayList.java:831)。
按照java的fail-fast机制中的介绍:
Iterator是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出java.util.ConcurrentModificationException异常。
最终判断出使用的SynchronizedList在遍历过程中其List出现了内容的变更。
为了确定问题所在,我们应该去看SynchronizedList的源码:
static class SynchronizedList<E> extends SynchronizedCollection<E> implements List<E> { private static final long serialVersionUID = -7754090372962971524L; final List<E> list; SynchronizedList(List<E> l) { super(l); list = l; } SynchronizedList(List<E> l, Object mutex) { super(l, mutex); list = l; } @Override public void add(int location, E object) { synchronized (mutex) { list.add(location, object); } } @Override public boolean addAll(int location, Collection<? extends E> collection) { synchronized (mutex) { return list.addAll(location, collection); } } @Override public boolean equals(Object object) { synchronized (mutex) { return list.equals(object); } } @Override public E get(int location) { synchronized (mutex) { return list.get(location); } } @Override public int hashCode() { synchronized (mutex) { return list.hashCode(); } } @Override public int indexOf(Object object) { final int size; final Object[] array; synchronized (mutex) { size = list.size(); array = new Object[size]; list.toArray(array); } if (object != null) { for (int i = 0; i < size; i++) { if (object.equals(array[i])) { return i; } } } else { for (int i = 0; i < size; i++) { if (array[i] == null) { return i; } } } return -1; } @Override public int lastIndexOf(Object object) { final int size; final Object[] array; synchronized (mutex) { size = list.size(); array = new Object[size]; list.toArray(array); } if (object != null) { for (int i = size - 1; i >= 0; i--) { if (object.equals(array[i])) { return i; } } } else { for (int i = size - 1; i >= 0; i--) { if (array[i] == null) { return i; } } } return -1; } @Override public ListIterator<E> listIterator() { synchronized (mutex) { return list.listIterator(); } } @Override public ListIterator<E> listIterator(int location) { synchronized (mutex) { return list.listIterator(location); } } @Override public E remove(int location) { synchronized (mutex) { return list.remove(location); } } @Override public E set(int location, E object) { synchronized (mutex) { return list.set(location, object); } } @Override public List<E> subList(int start, int end) { synchronized (mutex) { return new SynchronizedList<E>(list.subList(start, end), mutex); } } private void writeObject(ObjectOutputStream stream) throws IOException { synchronized (mutex) { stream.defaultWriteObject(); } } /** * Resolves SynchronizedList instances to SynchronizedRandomAccessList * instances if the underlying list is a Random Access list. * <p> * This is necessary since SynchronizedRandomAccessList instances are * replaced with SynchronizedList instances during serialization for * compliance with JREs before 1.4. * <p> * * @return a SynchronizedList instance if the underlying list implements * RandomAccess interface, or this same object if not. * * @see SynchronizedRandomAccessList#writeReplace() */ private Object readResolve() { if (list instanceof RandomAccess) { return new SynchronizedRandomAccessList<E>(list, mutex); } return this; } }
从源码中我们可以看到SynchronizedList是通过对mutex做同步来控制线程安全的,而mutex定义在其父类SynchronizedCollection中。
SynchronizedCollection(Collection<E> collection) { c = collection; mutex = this; } SynchronizedCollection(Collection<E> collection, Object mutex) { c = collection; this.mutex = mutex; }
从源码中,我们发现了add、remove等操作都是线程安全的,加锁的对象默认是this,也即是list本身。但是没有针对Iterator.next做同步处理。所以整个for循环是非线程安全的。
另外,需要注意的是add、remove等操作仅是方法安全,如果在使用过程中执行如下代码:
list.add(object1);
list.remove(object1);
此代码并非原子操作,任何线程都可能在add和remove之间抢夺mutex。
找到了问题,那如何解决呢?上面描述中,SynchronizedList默认以List本身做为锁对象,所以只需要在遍历的代码中对本身list做synchronized处理即可。或者自定义mutex。
- SynchronizedList的同步问题
- 线程安全的Collections.synchronizedList
- SynchronizedList和Vector的区别
- CopyOnWriteArrayList与Collections.synchronizedList的性能对比
- 【集合类型的并发】Collections.synchronizedList
- CopyOnWriteArrayList与Collections.synchronizedList的性能对比
- Collections.synchronizedList()不同锁造成的陷阱
- CopyOnWriteArrayList与Collections.synchronizedList的性能对比
- CopyOnWriteArrayList与Collections.synchronizedList的性能对比
- 【Java集合类型的并发】Collections.synchronizedList
- CopyOnWriteArrayList与Collections.synchronizedList的性能对比
- CopyOnWriteArrayList与Collections.synchronizedList的性能对比
- 【集合类型的并发】Collections.synchronizedList
- Collections.synchronizedList()不同锁造成的陷阱
- CopyOnWriteArrayList与Collections.synchronizedList的性能对比
- 【集合类型的并发】Collections.synchronizedList 的使用
- 通过synchronizedList将List转变成线程安全的List
- Hashtable的同步问题
- JavaScript正则表达式详解
- prettydate.js 插件
- Java实现自己的Json解析器——Json字符串解析原理
- C++实现一个string类
- 从搭建一个React项目,同时使用git把项目放到GitHub上
- SynchronizedList的同步问题
- PHP laravel系列之Blade模版
- jquery.treeview.js 插件
- spring boot跳过maven test
- DWR第三篇之逆向Ajax升级
- 用keras实现3层BP网络的训练、保存、加载和导入自己手写的数字进行测试
- mybatis VS hibernate
- Gogs搭建git服务器
- android 程序永远保持屏幕亮