转载自:https://blog.csdn.net/qq_34302506/article/details/79514920

1、集合和Map
    下图是Java集合的Collection集合体系的继承树:
    下图是Java的Map体系的继承树:
    对于Set、List、Queue和Map四种集合,最常用的是HashSet、TreeSet、ArrayList、ArrayQueue、LinkedList和HashMap、TreeMap等实现类。
    其中Vector、HashTable、Properties是线程安全的。其中ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap等都是线程不安全的。(线程不安全是指:当多个线程访问同一个集合或Map时,如果有超过一个线程修改了ArrayList集合,则程序必须手动保证该集合的同步性。)
2、包装线程不安全的集合
    当多个并发向这些集合中存、取元素时,就可能会破坏这些集合的数据完整性。
    如果程序中有多个线程可能访问以上这些集合,就可以使用Collections提供的类方法把这些集合包装成线程安全的集合。Collections提供了如下几个静态方法。
  • <T> Collection<T> synchronizedCollection(Collection<T> c): 返回指定collection 对应的线程安全的collection。
  • static <T> List<T> synchronizedList(List<T> list): 返回指定List对象对应的线程安全的List 对象。
  • static <K, V> Map<K, V> synchronizedMap(Map<K, V> m): 返回指定Map对象对应的线程安全的Map对象。
  • static <T> Set<T> synchronizedSet(Set<T> s): 返回指定Set对象对应的线程安全的Set对象。
  • static <K, V> SortedMap<K, V> synchronizedSortedMap(SortedMap<K, V> m): 返回指定SortedMap对象对应的线程安全的SortedMap对象。
  • static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s): 返回指定SortedSet对象对应的线程安全的SortedSet对象。
    例如需要在多线程里使用线程安全的HashMap对象(如果需要把某个集合包装成线程安全的集合,则应该在创建之后立即包装,如下程序所示),则可以采用如下代码:
    // 使用Collections 的 synchronizedMap 方法将一个普通的HashMap包装成线程安全的类
    HashMap m = Collections.synchronizedMap(new HashMap());
3、线程安全的集合类
    从Java 5 开始,在java.util.concurrent 包下提供了大量支持高效并发访问的集合接口和实现类,如下图所示:
    从上图中所示的类图可以看出,这些线程安全的集合类可以分为如下两类。
  • 以Concurrent 开头的集合类,如ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、ConcurrentLinkedQueue 和 ConcurrentLinkedDeque。
  • 以CopyOnWrite 开头的集合类,如CopyOnWriteArrayList、CopyOnWriteArraySet。
    其中以Concurrent 开头的集合类代表了支持并发访问的集合,它们可以支持多个线程并发写入访问,这些写入线程的所有操作都是线程安全的,但读取操作不必锁定。以Concurrent 开头的集合类采用了更复杂的算法来保证永远不会锁住整个集合,因此在并发写入时有较好的性能。
    当多个线程共享访问一个公共集合时,ConcurrentLinkedQueue 是一个恰当的选择。ConcurrentLinkedQueue集合时无需等待。
    在默认情况下,ConcurrentHashMap 支持16个线程并发写入,当有超过16 个线程并发向该Map 中写入数据时,可能有一些线程需要等待。实际上,程序通过设置concurrentLevel 构造参数(默认值为16)来支持更多的并发写入线程。
    与前面介绍的HashMap 和普通集合不同的是,因为ConcurrentLinkedQueue 和 ConcurrenthashMap 支持多线程并发访问,所以当使用迭代来遍历集合时,该迭代器可能不能反映出创建迭代器之后所做的修改,但程序不会抛出任何异常。