安全和不安全容器

1,441 阅读3分钟

引言:更多相关请看 JAVA并发编程系列

概述

指容器在并发修改争抢下会不会出现java.util.ConcurrentModificationException:并发修改异常。会就是不安全容器,不会(正常执行)就是安全容器。

List

不安全:ArrayList、LinkedList
安全:
Vector:1.0出现,Add方法加synchronized修饰。

Collections.synchronizedCollection/List():synchronizedCollection是synchronizedList和synchronizedSet的父类。把一个普通不安全的的List封装成线程安全的List,Add方法加synchronized修饰。
CopyOnWriteArrayList:写时复制思想,add方法加了ReentrantLock可重入锁。
Lists.newCopyOnWriteArrayList():使用需引入google的guava依赖包,底层是CopyOnWriteArrayList。

Set

不安全:HashSet、LinkedHashSet、TreeSet。
安全:
Collections.synchronizedCollection/Set():synchronizedCollection是synchronizedList和synchronizedSet的父类。把一个普通不安全的的Set封装成线程安全的Set,Add方法加synchronized修饰。
CopyOnWriteArraySet:底层是CopyOnWriteArrayList()。

下面两个需引入google的guava依赖包才能使用:
Sets.newCopyOnWriteArraySet():底层是CopyOnWriteArraySet。
Sets.newConcurrentHashSet():底层是ConcurrentHashMap。

Map

不安全:HashMap、LinkedHashMap、TreeMap。
安全:
Hashtable:put方法了synchronized修饰。

ConcurrentHashMap:分段锁思想,提高效率。底层也加了synchronized修饰的同步代码块。
Collections.synchronizedMap:put方法里加synchronized修饰。
Maps.newConcurrentMap():使用需引入google的guava依赖包,底层是ConcurrentHashMap。

代码案例

// 安全与不安全容器
class ContainerDemo {
    public static void main(String[] args) {
        // 测试可能会爆java.util.ConcurrentModificationException:并发修改异常就是不安全容器,正常执行、完全不会爆就是安全容器
//        listDemo();
//        setDemo();
        mapDemo();
    }

    /**
     * 安全与不安全List
     */
    public static void listDemo() {
        // 不安全List
//        List<String> list = new ArrayList<>();
//        List<String> list = new LinkedList<>();
        // 安全List
//        List<String> list = new Vector<>();
//        Collection<Object> list = Collections.synchronizedCollection(new ArrayList<>());
//        List<String> list = Collections.synchronizedList(new ArrayList<>());
//        List<String> list = new CopyOnWriteArrayList<>();
        // 需引入google的guava依赖包
        List<String> list = Lists.newCopyOnWriteArrayList();
        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();
        }
    }

    /**
     * 安全与不安全Set
     */
    public static void setDemo() {
        // 不安全Set
//        Set<String> set = new HashSet<>();
//        Set<String> set = new LinkedHashSet<>();
//        Set<String> set = new TreeSet<>();
        // 安全Set
//        Collection<Object> set = Collections.synchronizedCollection(new HashSet<>());
//        Set<String> set = Collections.synchronizedSet(new HashSet<>());
//        Set<Object> objects = Collections.synchronizedSet(new HashSet<>());
//        Set<String> set = new CopyOnWriteArraySet<>();
        // 需引入google的guava依赖包
//        Set<String> set = Sets.newCopyOnWriteArraySet();
        Set<String> set = Sets.newConcurrentHashSet();
        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();
        }
    }

    /**
     * 安全与不安全Map
     */
    public static void mapDemo() {
        // 不安全Map
//        Map<Object, Object> map = new HashMap<>();
//        Map<Object, Object> map = new LinkedHashMap<>();
//        Map<Object, Object> map = new TreeMap<>();
        // 安全Map
//        Map<Object, Object> map = new Hashtable<>();
//        Map<Object, Object> map = new ConcurrentHashMap<>();
//        Map<Object, Object> map = Collections.synchronizedMap(new HashMap<>());
        // 需引入google的guava依赖包
        Map<Object, Object> map = Maps.newConcurrentMap();
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                map.put(UUID.randomUUID().toString().substring(0,3),UUID.randomUUID().toString().substring(0,8));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

总结

通过查看源码发现:安全容器的底层的get/put方法上或方法里总是有加锁。synchronized关键字或ReentrantLock可重入锁。也可以理解为:安全的本质就是加锁,避免并发修改争抢导致java.util.ConcurrentModificationException:并发修改异常。

写时复制

CopyOnWriteArrayList类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容器也是一种读写分离的思想,读和写不同的容器。
与Vector相比:Vector所有的读写方法都加synchronized修饰

CopyOnWriteArrayList只有修改加ReentrantLock锁,读的方法不加锁,效率更高。