文章目录- I . Java 集合的线程安全概念 ( 加锁同步 )
- II . 线程不安全集合 ( 没有并发需求 推荐使用 )
- III . 集合属性说明
- IV . 早期的线程安全集合 ( 不推荐使用 )
- V . 推荐使用的线程安全集合 ( 推荐使用 )
- VI . CopyOnWrite 机制
I . Java 集合的线程安全概念 ( 加锁同步 )
1 . 线程安全问题引入 : 使用 Java 集合时 , 不可避免的要在多线程访问集合 , 如果线程安全处理不当 , 就会造成不可预知的故障 ;
2 . 加锁阻塞实现线程安全 : 当多线程操作 Java 集合时 , 使用 synchronized 关键字 加锁阻塞任何对集合的操作 , 修改完毕后 , 解除阻塞 , 防止出现多线程操作 , 出现数据污染 ;
3 . 加锁前后性能对比 : 如果将集合加锁 , 显然会降低程序的性能 , 普通集合 要比 线程安全集合 性能高 ;
4 . 线程安全与性能最佳实践 :
① 线程不安全操作 ( 保证性能 ) : 如果不需要多线程操作集合 , 那么直接使用线程不安全集合即可 , 使性能达到最高 ;
② 线程安全操作 ( 保证正确性 ) : 尽量避免自己手动使用 synchronized 关键字加锁 , synchronized 开销很大 , 消耗性能 ; 推荐使用 JDK 中提供的 java.util.concurrent 包线程安全集合 ;
II . 线程不安全集合 ( 没有并发需求 推荐使用 )
线程不安全的集合 : Java 中的最基础的集合 , 如果没有并发需求 , 推荐使用这些集合 , 其性能高 ; 这些类都定义在 java.utils 包下 ; 线程安全集合都定义在 java.util.concurrent 包下 ;
1 . List 集合 : ArrayList , LinkedList ; 有序元素集合 , 每个元素都有一个索引 ( 从 0 计数 ) ;
① ArrayList : 底层数据结构是 数组 , 通过下标可快速查询 , 查询 修改 性能高 , 增加 删除 性能低 ;
② LinkedList : 底层数据结构是 链表 , 查询 修改 性能低 , 增加 删除 性能高 ;
2 . Set 集合 : HashSet , TreeSet ; 不能包含重复元素 ; 注意 null 元素也算一个元素 , 只能有一个 ;
① HashSet : 底层数据结构是 哈希表 ; 元素无序 , 唯一 ; 元素类需要重写 hashCode 和 equals 两个方法 , 保证唯一性 ;
② TreeSet : 底层数据结构是 红黑树 ; 元素有序 , 唯一 ; 元素类需要重写 hashCode 和 equals 两个方法 , 保证唯一性 ; 使用排序机制 ( 自然排序 / 比较器排序 ) 保证有序性 ;
- 自然排序 : 元素类需要实现 Compareable 接口 , 覆盖 compareTo 方法 ; 两个排序策略二选一即可 ;
- 比较器排序 : TreeSet 初始化时 , 设置 Comparator 比较器 ; Comparator 是接口 , 需重写 compare 方法 ;
③ LinkedHashSet : 底层数据结构是 哈希表 和 链表 ; HashSet 派生类 , 其操作与 HashSet 一致 , 没有定义额外的方法 ;
HashSet 与 TreeSet 二者对比 : HashSet 性能高于 TreeSet , 但是不能排序 ;
3 . Map 集合 : HashMap , LinkedHashMap , TreeMap ;
① HashMap : 键 ( Key ) 使用哈希表维护 , 注意元素存放顺序 , 其中的 元素添加顺序 并不是 数据存放顺序 ;
② LinkedHashMap : HashMap 派生类 , Key 除了使用 哈希表 保证其唯一性之外 , 还使用 链表 保证了其 顺序性 , 元素添加顺序 ( 访问顺序 ) 就是 数据的存放顺序 ;
③ TreeMap : Key 使用红黑树维护 , Key 需要使用排序机制 ( 自然排序 / 比较器排序 ) 保证有序性 ;
- 自然排序 : 元素类需要实现 Compareable 接口 , 覆盖 compareTo 方法 ; 两个排序策略二选一即可 ;
- 比较器排序 : TreeSet 初始化时 , 设置 Comparator 比较器 ; Comparator 是接口 , 需重写 compare 方法 ;
上述 Hash , Tree , LinkedHash 等都是针对键 ( Key ) 进行维护的 ; 即使用 哈希表 , 红黑树 , 链表哈希表维护键 ( Key ) ;
III . 集合属性说明
1 . Hash : 使用哈希表实现 , 如 HashSet , HashMap , 目的是为了保证其元素唯一性 ;
① 特点 : 元素唯一 ;
① 定制 : 需要保证唯一元素类需要重写 hashCode 和 equals 两个方法 ;
2 . Array : 使用数组实现 , 如 ArrayList ;
① 特点 : 有序 , 下标访问 ;
3 . Linked : 使用链表实现 , 如 LinkedList , 其目的是为了保证有序性 ;
① 特点 : 有序 ;
4 . Tree : 使用红黑树实现 , 如 TreeSet , TreeMap , 其目的是为了保证插入的元素自动排序 ;
① 特点 : 自动排序 ;
② 实现 : 使用排序机制 ( 自然排序 / 比较器排序 ) 保证有序性 ;
- 自然排序 : 元素类需要实现 Compareable 接口 , 覆盖 compareTo 方法 ; 两个排序策略二选一即可 ;
- 比较器排序 : TreeSet 初始化时 , 设置 Comparator 比较器 ; Comparator 是接口 , 需重写 compare 方法 ;
IV . 早期的线程安全集合 ( 不推荐使用 )
下面讲的 Vector , HashTable 集合虽然线程安全 , 但是性能很低 , 不推荐使用 ; 已经弃用的类就不再详细解析了 ;
1 . 线程安全 List 集合 :
① Vector ( 弃用 ) : 与 ArrayList 功能与实现基本相同 , 方法前使用 synchronize 关键字同步 ;
② Stack : 栈 结构 , 元素后进先出 , Vector 子类 , 是线程安全的 ;
2 . 线程安全 Map 集合 : HashTable ( 弃用 ) , 与 HashMap 功能相同 , 方法前使用 synchronize 关键字同步 ; HashTable 键值都不能为空 , 否则会报空指针异常 ;
V . 推荐使用的线程安全集合 ( 推荐使用 )
java.util.concurrent 包提供了一系列线程并发工具 , 如 并发锁 , 执行器 , 原子类 , 并发控制类 , 阻塞队列 , 并发集合 , 这里我们先讨论并发集合 , 其余在 Java 并发 中研究 ;
1 . 推荐使用的线程安全集合 : java.util.concurrent 包下的 线程安全集合 ;
① 实现原理 : 也是通过加锁实现线程安全 , 但是其加锁力度很细 , 如区分读写加锁 , 分段加锁 ;
② 优势 : 兼顾了性能与线程安全 , 在保证线程安全的前提下 , 最大限度提高性能 ;
③ 注意 : 这些线程安全集合性能低于普通集合 , 如果不需要多线程访问 , 优先使用普通集合 ;
2 . 与早期的线程安全集合对比 :
① 早期的线程安全集合 : 全部操作都加锁 , 多线程访问几乎每个操作都会阻塞 , 性能很低 ;
② java.util.concurrent 包的线程安全集合 : 加锁的力度很细 , 大部分的线程操作不会加锁 , 没有冲突 , 性能比早期线程略高 , 比现场不安全的线程低 ;
3 . concurrent 包的线程安全集合 : concurrent 包中提供了一系列线程安全的集合 , List , Map , Set , Queue 都有对应的线程安全容器 ;
① List 集合 : CopyOnWriteArrayList ;
② Set 集合 : CopyOnWriteArraySet , ConcurrentSkipListSet ;
③ Map 集合 : ConcurrentMap , ConcurrentHashMap , ConcurrentSkipListMap , ConcurrentNavigableMap ;
④ Queue 队列 : ConcurrentLinkedQueue , ConcurrentLinkedDeque ;
VI . CopyOnWrite 机制
1 . 集合元素修改 ( 加锁并复制 ) : 顾名思义就是在修改集合中的元素时 , 不直接操作当前的集合 , 而是先把集合拷贝一份 , 然后在新的集合中进行修改操作 , 最后将引用指向新的集合 ;
① 修改操作 : add , remove , clear , set 等操作都是加锁 ;
② 本质 : 相当于每次修改都创建了一个新集合 ;
③ 写入互斥 : 多个线程同时修改集合的数据是互斥的 ;
2 . 集合元素读取 ( 不加锁 ) : CopyOnWrite 集合的 get 方法不加锁 , 因为其修改集合时不是修改当前的集合 , 当前集合不会出现数据污染 ;
① 同时读取 : CopyOnWrite 集合支持多个线程同时读取集合数据 ;
3 . 缺陷 :
① 性能 : 每次修改集合 , 都要将整个集合复制一次 , 如何集合很大 , 并且修改频繁 , 那么会导致性能很低 ;
② 实时性 : 读取的时候 , 有可能线程正在被修改 , 读取完毕后 , 有可能集合已经更新了 , 当前读取的数据已经过时 , 不能保证数据的实时性 ;
4 . 适用场景 : CopyOnWrite 机制的集合适用于查询多 , 修改少 , 数据量小的集合 ; 数据量过大 , 或修改频繁 , 性能很低 ;