Java多线程下集合类并发修改异常处理

mac2024-06-02  59

ArrayList是线程不安全的,因为在他的 add 方法添加元素时,为了保证并发性,没有加锁。

public class CollectionTest { public static void main(String[] args) { final List<String> list = new ArrayList<>(); for (int i = 0; i <20; i++) { //创建20个线程 new Thread(new Runnable() { @Override public void run() { //list添加随机字符串 list.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(list); } }).start(); } } } - 打印结果-- - -- - - - - - - - java.util.ConcurrentModificationException 高并发下的修改异常

解决方案一:用 Vector 代替 ArrayList ,Vector相较于ArrayList是线程安全的,Vector 所有针对集合内元素的操作方法都是由 synchronized 修饰的。但是!!Vector 是JDK1.0引入的,ArrayList是1.2引入的。为何有线程安全的还要新出一个不安全的?

                      Vector 的缺点:由于加了synchronized,导致失去了并发性,效率大大下降。

解决方案二:用 Collections.synchronizedList(new ArrayList<String>()) 代替new ArrayList<String>()。

public class CollectionTest { public static void main(String[] args) { // final List<String> list = new ArrayList<>(); // final List<String> list = new Vector<>(); final List<String> list = Collections.synchronizedList(new ArrayList<String>()); for (int i = 0; i <20; i++) { new Thread(new Runnable() { @Override public void run() { list.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(list); } }).start(); } } }

解决方案三:用CopyOnWriteArrayList(写时复制)代替ArrayList。

public class CollectionTest { public static void main(String[] args) { //final List<String> list = new ArrayList<>(); //final List<String> list = new Vector<>(); //final List<String> list = Collections.synchronizedList(new ArrayList<String>()); final List<String> list = new CopyOnWriteArrayList<>(); for (int i = 0; i <20; i++) { new Thread(new Runnable() { @Override public void run() { list.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(list); } }).start(); } } }

CopyOnWriteArrayList源码分析

public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8673264195747942595L; /** * The lock protecting all mutators. (We have a mild preference * for builtin monitors over ReentrantLock when either will do.) */ final transient Object lock = new Object(); /** The array, accessed only via getArray/setArray. */ // Android-changed: renamed array -> elements for backwards compatibility b/33916927 private transient volatile Object[] elements; //volatile修饰,保证其可见性 ... ... /** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return {@code true} (as specified by {@link Collection#add}) */ public boolean add(E e) { synchronized (lock) { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } } ... ... 首先,底层数组被volatile修饰,保证其可见性有序性,也就是保证了在多并发的情况下数据的一致性;其次,在add方法添加数据的时候,使用了同步锁,保证了其原子性;最后,在往数组中添加元素时,并不直接往当前的数组里添加,而是复制出一个新的数组 newElements ,新数组长度+1,并将元素赋值给数组最后一位,添加完元素之后,再将原数组的引用指向新数组 setArray(newElements)。

       这样做的好处是可以对CopyOnWrite容器进行并发的读而不需要加锁,因为当前容器不会添加任何元素,这是一种读写分离的思想,读和写分别在不同的容器完成。

      另外,像其他的数据结构:HashMap、HashSet,其底层都是不安全的,所能引起的异常和ArrayList一样,都是

java.util.ConcurrentModificationExecption,并且都有其相应的解决方法:ConcurrentHashMap、CopyOnWriteArraySet

CopyOnWriteArraySet 搞笑源码

public class CopyOnWriteArraySet<E> extends AbstractSet<E> implements java.io.Serializable { private static final long serialVersionUID = 5457747651344034263L; private final CopyOnWriteArrayList<E> al; /** * Creates an empty set. */ public CopyOnWriteArraySet() { al = new CopyOnWriteArrayList<E>(); } }

       发现其底层还是个 CopyOnWriteArrayList ... ...换汤不换药... ...

========================================================================

附加知识:HashSet底层是HashMap,证据如下源码

public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { static final long serialVersionUID = -5024744406713321676L; private transient HashMap<E,Object> map; // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object(); /** * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has * default initial capacity (16) and load factor (0.75). */ public HashSet() { map = new HashMap<>(); } }

      那么问题来了!!!

  HashMap添加元素是map.put(key,value);而HashSet添加元素是set.add(Element);这两个完全不一样,怎么能联系到一起

话不多说,还是看源码

public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { static final long serialVersionUID = -5024744406713321676L; private transient HashMap<E,Object> map; // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object(); public boolean add(E e) { return map.put(e, PRESENT)==null; } }

   什么鬼!?HashSet的add方法里居然是HashMap的put方法,元素e添加到了key的位置,value的位置是个常量类型的Object,

名字叫PRESENT——礼物 ... ...

 

 

最新回复(0)