SynchronizedList的同步问题

举报
轻狂书生FS 发表于 2020/12/03 00:57:12 2020/12/03
【摘要】 ArrayList是非线程安全的,在多线程中同时操作ArrayList会经常出现ConcurrentModificationException。为了解决同步问题,java提供了Collections的同步类:SynchronizedList、SynchronizedMap、SynchronizedSet等。以SynchronizedList为例,从字面意义上看,应该是线程安全...

ArrayList是非线程安全的,在多线程中同时操作ArrayList会经常出现ConcurrentModificationException。为了解决同步问题,java提供了Collections的同步类:SynchronizedList、SynchronizedMap、SynchronizedSet等。以SynchronizedList为例,从字面意义上看,应该是线程安全的。在我们的代码中,我们使用了SynchronizedList,初始代码如下: 
List list = Collections.synchronizedList(new ArrayList()); 
在运用过程中,不对该List加锁处理。APP上线后,定时查看后台返回的crash信息,发现对该list的操作依然出现了ConcurrentModificationException。crash信息中显示该异常发生在执行for循环时:


  
  1.  for(Object object : list){
  2.     ....
  3.  }


错误的最后一句执行代码为:java.util.ArrayList$Itr.next(ArrayList.java:831) 
看来SynchronizedList并不像想象的那样绝对保证线程安全,那问题如何出现的呢? 
先从增强for循环的实现说起。 
增强for循环是基于迭代器实现的,如原始代码为:


  
  1.  for (Integer i : list) {
  2.        System.out.println(i); 
  3.  }


反编译后,可以看到如下代码:


  
  1. Integer i;
  2. for(Iterator iterator = list.iterator(); iterator.hasNext(); System.out.println(i)){
  3.         i = (Integer)iterator.next(); 
  4. }


而我们的crash最后一句就是java.util.ArrayList$Itr.next(ArrayList.java:831)。 
按照java的fail-fast机制中的介绍:

Iterator是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出java.util.ConcurrentModificationException异常。

最终判断出使用的SynchronizedList在遍历过程中其List出现了内容的变更。 
为了确定问题所在,我们应该去看SynchronizedList的源码:


  
  1. static class SynchronizedList<E> extends SynchronizedCollection<E> implements List<E> {
  2.         private static final long serialVersionUID = -7754090372962971524L;
  3.         final List<E> list;
  4.         SynchronizedList(List<E> l) {
  5.             super(l);
  6.             list = l;
  7.         }
  8.         SynchronizedList(List<E> l, Object mutex) {
  9.             super(l, mutex);
  10.             list = l;
  11.         }
  12.         @Override public void add(int location, E object) {
  13.             synchronized (mutex) {
  14.                 list.add(location, object);
  15.             }
  16.         }
  17.         @Override public boolean addAll(int location, Collection<? extends E> collection) {
  18.             synchronized (mutex) {
  19.                 return list.addAll(location, collection);
  20.             }
  21.         }
  22.         @Override public boolean equals(Object object) {
  23.             synchronized (mutex) {
  24.                 return list.equals(object);
  25.             }
  26.         }
  27.         @Override public E get(int location) {
  28.             synchronized (mutex) {
  29.                 return list.get(location);
  30.             }
  31.         }
  32.         @Override public int hashCode() {
  33.             synchronized (mutex) {
  34.                 return list.hashCode();
  35.             }
  36.         }
  37.         @Override public int indexOf(Object object) {
  38.             final int size;
  39.             final Object[] array;
  40.             synchronized (mutex) {
  41.                 size = list.size();
  42.                 array = new Object[size];
  43.                 list.toArray(array);
  44.             }
  45.             if (object != null) {
  46.                 for (int i = 0; i < size; i++) {
  47.                     if (object.equals(array[i])) {
  48.                         return i;
  49.                     }
  50.                 }
  51.             } else {
  52.                 for (int i = 0; i < size; i++) {
  53.                     if (array[i] == null) {
  54.                         return i;
  55.                     }
  56.                 }
  57.             }
  58.             return -1;
  59.         }
  60.         @Override public int lastIndexOf(Object object) {
  61.             final int size;
  62.             final Object[] array;
  63.             synchronized (mutex) {
  64.                 size = list.size();
  65.                 array = new Object[size];
  66.                 list.toArray(array);
  67.             }
  68.             if (object != null) {
  69.                 for (int i = size - 1; i >= 0; i--) {
  70.                     if (object.equals(array[i])) {
  71.                         return i;
  72.                     }
  73.                 }
  74.             } else {
  75.                 for (int i = size - 1; i >= 0; i--) {
  76.                     if (array[i] == null) {
  77.                         return i;
  78.                     }
  79.                 }
  80.             }
  81.             return -1;
  82.         }
  83.         @Override public ListIterator<E> listIterator() {
  84.             synchronized (mutex) {
  85.                 return list.listIterator();
  86.             }
  87.         }
  88.         @Override public ListIterator<E> listIterator(int location) {
  89.             synchronized (mutex) {
  90.                 return list.listIterator(location);
  91.             }
  92.         }
  93.         @Override public E remove(int location) {
  94.             synchronized (mutex) {
  95.                 return list.remove(location);
  96.             }
  97.         }
  98.         @Override public E set(int location, E object) {
  99.             synchronized (mutex) {
  100.                 return list.set(location, object);
  101.             }
  102.         }
  103.         @Override public List<E> subList(int start, int end) {
  104.             synchronized (mutex) {
  105.                 return new SynchronizedList<E>(list.subList(start, end), mutex);
  106.             }
  107.         }
  108.         private void writeObject(ObjectOutputStream stream) throws IOException {
  109.             synchronized (mutex) {
  110.                 stream.defaultWriteObject();
  111.             }
  112.         }
  113.         /**
  114.          * Resolves SynchronizedList instances to SynchronizedRandomAccessList
  115.          * instances if the underlying list is a Random Access list.
  116.          * <p>
  117.          * This is necessary since SynchronizedRandomAccessList instances are
  118.          * replaced with SynchronizedList instances during serialization for
  119.          * compliance with JREs before 1.4.
  120.          * <p>
  121.          *
  122.          * @return a SynchronizedList instance if the underlying list implements
  123.          *         RandomAccess interface, or this same object if not.
  124.          *
  125.          * @see SynchronizedRandomAccessList#writeReplace()
  126.          */
  127.         private Object readResolve() {
  128.             if (list instanceof RandomAccess) {
  129.                 return new SynchronizedRandomAccessList<E>(list, mutex);
  130.             }
  131.             return this;
  132.         }
  133.     }


从源码中我们可以看到SynchronizedList是通过对mutex做同步来控制线程安全的,而mutex定义在其父类SynchronizedCollection中。


  
  1. SynchronizedCollection(Collection<E> collection) {
  2.             c = collection;
  3.             mutex = this;
  4. }
  5.  SynchronizedCollection(Collection<E> collection, Object mutex) {
  6.             c = collection;
  7.             this.mutex = mutex;
  8. }


从源码中,我们发现了add、remove等操作都是线程安全的,加锁的对象默认是this,也即是list本身。但是没有针对Iterator.next做同步处理。所以整个for循环是非线程安全的。 
另外,需要注意的是add、remove等操作仅是方法安全,如果在使用过程中执行如下代码: 
list.add(object1); 
list.remove(object1); 
此代码并非原子操作,任何线程都可能在add和remove之间抢夺mutex。

找到了问题,那如何解决呢?上面描述中,SynchronizedList默认以List本身做为锁对象,所以只需要在遍历的代码中对本身list做synchronized处理即可。或者自定义mutex。


--------------------- 
作者:hanzengbo2017 
来源:CSDN 
原文:https://blog.csdn.net/hanzengbo2017/article/details/77801110 
版权声明:本文为博主原创文章,转载请附上博文链接!

文章来源: blog.csdn.net,作者:轻狂书生FS,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/LookForDream_/article/details/90242304

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。