通常使用的集合类是不安全的比如ArrayList,HashSet,HashMap等

当我们同时对其进行修改并读取时往往会抛出java.util.ConcurrentModificationException异常 这个异常就叫做并发修改异常

究其原因:

#### 我们可以看到ArrayList底层没有synchronized,导致线程不安全

解决方案

使用Vector集合线程安全

底层有synchronized,所以线程安全

Collections工具类提供同步方法

Collections提供了方法_synchronizedList保证list是同步线程安全的_ 使用时 List list =Collections.synchronizedList(**new **ArrayList());

写时复制

当我们不加锁时性能好但是会出现错误,当我们加锁时也会导致性能下降,所以JUC包中提供了可一边写一遍读的类,如CopyOnWriteArrayList类,他实现了读写分离,既不会出现线程安全问题也不会让性能下降严重 官方API英文 A thread-safe variant of ArrayList in which all mutative operations (add, set, and so on) are implemented by making a fresh copy of the underlying array. CopyOnWriteArrayList是arraylist的一种线程安全变体,其中所有可变操作(add、set等)都是通过生成底层数组的新副本来实现的。 其实就是在内部又创建了一个复制数组,每次改之前都将数据复制到一个新数组当中,这个新数组只供读不上锁,写数组上锁后只负责写

举例图解

/**
 * 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) {
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();
    }
}


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

扩展类比

Set set = new HashSet();//线程不安全


Set set = new CopyOnWriteArraySet();//线程安全
HashSet底层数据结构是什么?
HashSet底层是一个HashMap
但HashSet的add是放一个值,而HashMap是放K、V键值对?
这是因为HashSet的val就是Map的key,而底层map的val一直是一共常量
public HashSet() {
	map = new HashMap();
}
private static final Object PRESENT = new Object();
public boolean add(E e) {
	return map.put(e, PRESENT)==null;
}

Map map = new HashMap();//线程不安全


Map map = new ConcurrentHashMap();//线程安全


具体代码和测试如下

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * 请举例说明集合类是不安全的
 */
public class NotSafeDemo {
    public static void main(String[] args) {

        Map map = new ConcurrentHashMap();
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));
                System.out.println(map);
            }, String.valueOf(i)).start();
        }


    }

    private static void setNoSafe() {
        Set set = new CopyOnWriteArraySet();
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(set);
            }, String.valueOf(i)).start();
        }
    }

    private static void listNoSafe() {
//        List list = Arrays.asList("a","b","c");
        //        list.forEach(System.out::println);当我们同时对其进行修改并读取时往往会抛出java.util.ConcurrentModificationException异常
        //这个异常就叫做并发修改异常
        //写时复制
        List list = new CopyOnWriteArrayList();
// new CopyOnWriteArrayList();
        //Collections.synchronizedList(new ArrayList());
        //new Vector();//new ArrayList();

        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }


}


/**
 * 写时复制
 * CopyOnWrite容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,
 * 而是先将当前容器Object[]进行Copy,复制出一个新的容器Object[] newElements,然后向新的容器Object[] newElements里添加元素。
 * 添加元素后,再将原容器的引用指向新的容器setArray(newElements)。
 * 这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。
 * 所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
 * <p>
 * <p>
 * <p>
 * <p>
 * <p>
 * <p>
 * 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();
 * }
 * }
 */