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

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——礼物 ... ...

猜你喜欢

转载自blog.csdn.net/S_Alics/article/details/102841101
今日推荐