juc · 2019-09-17 0

ArrayList线程不安全及解决办法

1、ArrayList线程不安全

代码:

public class ContainerNotSafeTest {

    public static void main(String[] args) throws InterruptedException {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 30; i++) {
            int tempInt = i;
            new Thread(() -> {
                list.add(tempInt);
            }).start();
        }

        TimeUnit.SECONDS.sleep(2);
        System.out.println(list);
    }
}

结果:

[null, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 19, 21, 22, 23, 24, 25, 26, 27, 28, 29]

代码:

创建30个线程,向ArrayList集合添加元素

public class ContainerNotSafeTest {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        for(int i = 0; i < 30; i++){
            int tempInt = i;
            new Thread(()->{
                list.add(tempInt);
                System.out.println(list);
            }).start();
        }
    }
}

结果:

出现ConcurrentModificationException,因为并发争抢修改,导致并发修改异常

[0, 1]
[0, 1, 2, 3, 4]
[0, 1, 2, 3]
Exception in thread "Thread-2" [0, 1]
[0, 1, 2, 3, 4, 5]
[0, 1, 2, 3, 4, 5, 6]
[0, 1, 2, 3, 4, 5, 6, 7]
[0, 1, 2, 3, 4, 5, 6, 7, 8]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
Exception in thread "Thread-13" [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19]Exception in thread "Thread-18" 
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 24]
Exception in thread "Thread-25" [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 16]
java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
    at java.util.ArrayList$Itr.next(ArrayList.java:861)
    at java.util.AbstractCollection.toString(AbstractCollection.java:461)
    at java.lang.String.valueOf(String.java:2994)
    at java.io.PrintStream.println(PrintStream.java:821)
    at com.example.ContainerNotSafeTest.lambda$main$0(ContainerNotSafeTest.java:14)
    at java.lang.Thread.run(Thread.java:748)

2、Vector线程安全

把 ArrayList 改成 Vector

List<String> list = new Vector<>();

Vector、ArrayList都是以类似数组的形式存储在内存中,Vector和ArrayList提供的方法是差不多相同的,但Vector每个方法都是同步方法,添加元素的方法,多线程情况下,只允许一个线程进入,所以不会出现并发修改异常

Vector的add方法是同步方法:

// Vector.java
public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}

Vector的get方法是同步方法:

// Vector.java
public synchronized E get(int index) {
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);

    return elementData(index);
}

3、Collections的synchronizedList方法

使用Collections的synchronizedList,得到一个SynchronizedList,Collections内部类有SynchronizedList

List<String> list = Collections.synchronizedList(new ArrayList<>());

Collections的synchronizedList方法:

// Collections.java
public static <T> List<T> synchronizedList(List<T> list) {
    return (list instanceof RandomAccess ?
            new SynchronizedRandomAccessList<>(list) :
            new SynchronizedList<>(list));
}

SynchronizedList 的add方法与remove方法:

// SynchronizedList.java
public void add(int index, E element) {
    synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
    synchronized (mutex) {return list.remove(index);}
}

4、CopyOnWriteArrayList

CopyOnWriteArrayList写时复制

List<String> list = new CopyOnWriteArrayList<>();

CopyOnWrite容器即写时复制的容器,往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行复制,复制出一个新的容器Object[] newElements,然后新的容器Objet[] newElements里面添加元素,添加完元素之后,再将原容器的引用指向新的setArray(newElements);这样做的好处是可以对CopyOnWrite容器进行并发的读,是不需要加锁的,所有CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器

CopyOnWriteArrayList 的 add 方法:

// CopyOnWriteArrayList.java
public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

CopyOnWriteArrayList 的 get 方法:

// CopyOnWriteArrayList.java
public E get(int index) {
    return get(getArray(), index);
}

HashSet与HashMap

对于HashSet:

Set<String> set1 = new HashSet<>();  //底层new HashMap<>()
Set<String> set2 = Collections.synchronizedSet(new HashSet<>());
Set<String> set3 = new CopyOnWriteArraySet<>();    //底层CopyOnWriteArrayList

对于HashMap:

Map<String, String> map1 = new HashMap<>();
Map<String, String> map2= Collections.synchronizedMap(new HashMap<>());
Map<String, String> map3 = new ConcurrentHashMap<>();