【Java 集合】Java 集合的线程安全性 ( 加锁同步 | java.utils 集合 | 集合属性 | java.util.concurrent 集合 | CopyOnWrite 机制 )

简介: 【Java 集合】Java 集合的线程安全性 ( 加锁同步 | java.utils 集合 | 集合属性 | java.util.concurrent 集合 | CopyOnWrite 机制 )

文章目录

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 机制的集合适用于查询多 , 修改少 , 数据量小的集合 ; 数据量过大 , 或修改频繁 , 性能很低 ;


目录
相关文章
|
16天前
|
Java 程序员
深入理解Java异常处理机制
Java的异常处理是编程中的一块基石,它不仅保障了代码的健壮性,还提升了程序的可读性和可维护性。本文将深入浅出地探讨Java异常处理的核心概念、分类、处理策略以及最佳实践,旨在帮助读者建立正确的异常处理观念,提升编程效率和质量。
|
17天前
|
Java 开发者 UED
深入探索Java中的异常处理机制##
本文将带你深入了解Java语言中的异常处理机制,包括异常的分类、异常的捕获与处理、自定义异常的创建以及最佳实践。通过具体实例和代码演示,帮助你更好地理解和运用Java中的异常处理,提高程序的健壮性和可维护性。 ##
42 2
|
17天前
|
Java 开发者
Java中的异常处理机制深度剖析####
本文深入探讨了Java语言中异常处理的重要性、核心机制及其在实际编程中的应用策略,旨在帮助开发者更有效地编写健壮的代码。通过实例分析,揭示了try-catch-finally结构的最佳实践,以及如何利用自定义异常提升程序的可读性和维护性。此外,还简要介绍了Java 7引入的多异常捕获特性,为读者提供了一个全面而实用的异常处理指南。 ####
39 2
|
20天前
|
开发框架 安全 Java
Java 反射机制:动态编程的强大利器
Java反射机制允许程序在运行时检查类、接口、字段和方法的信息,并能操作对象。它提供了一种动态编程的方式,使得代码更加灵活,能够适应未知的或变化的需求,是开发框架和库的重要工具。
36 2
|
15天前
|
Java API 开发者
深入理解Java中的异常处理机制
本文探讨了Java编程语言中异常处理的核心概念,包括异常类型、异常捕获与抛出、以及最佳实践。通过分析常见的异常场景和处理策略,旨在帮助开发者更好地理解和运用异常处理机制,提高代码的健壮性和可维护性。文章不仅涵盖了基本的try-catch结构,还深入讨论了自定义异常的创建与使用,以及finally块的重要性和应用。此外,还将介绍一些高级技巧,如多异常捕获和嵌套异常处理,为读者提供全面的技术指导。
66 0
|
2月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
58 1
C++ 多线程之初识多线程
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
27 3
|
2月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
23 2
|
2月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
38 2
|
2月前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
44 1
下一篇
DataWorks