房屋建设设计网站,短期网页设计师培训,软文营销常用的方式,温州企业建站系统目录
并发容器
CopyOnWriteArrayList
应用场景
常用方法
读多写少场景使用CopyOnWriteArrayList举例 CopyOnWriteArrayList原理
CopyOnWriteArrayList 的缺陷
扩展迭代器fail-fast与fail-safe机制
ConcurrentHashMap
应用场景
常用方法
并发场景下线程安全举例
Con…目录
并发容器
CopyOnWriteArrayList
应用场景
常用方法
读多写少场景使用CopyOnWriteArrayList举例 CopyOnWriteArrayList原理
CopyOnWriteArrayList 的缺陷
扩展迭代器fail-fast与fail-safe机制
ConcurrentHashMap
应用场景
常用方法
并发场景下线程安全举例
ConcurrentHashMap底层结构 ConcurrentSkipListMap
应用场景
常用方法
跳表 并发容器 java中常用的集合ArrayList、LinkedList、HashMap这些容器都是非线程安全的。Java先提供了同步容器供使用。同步容器可以简单地理解为通过synchronized来实现同步的容器比如Vector、Hashtable以及SynchronizedList等容器。这样做的代价是削弱了并发性当多个线程共同竞争容器级的锁时吞吐量就会降低。 为了解决同步容器的性能问题有了并发容器java.util.concurrent包中提供了多种并发类容器。以下以部分并发容器详细介绍。
CopyOnWriteArrayList
应用场景
1. 读多写少的情况 当对列表的读操作远远多于写操作时CopyOnWriteArrayList可以提供良好的性能。由于读操作不涉及锁多个线程可以同时读取而写操作只需要在复制时进行同步。
2. 避免读写冲突 在某些情况下使用传统的同步容器可能会导致读写冲突因为读取和写入操作同时进行时可能会引发竞态条件。CopyOnWriteArrayList避免了这种冲突通过牺牲内存来保证读取的一致性。
3. 不需要实时更新的情况 如果对实时性要求不高即时写操作会导致列表被复制但读取操作仍然可以在原始列表上进行从而保持一致性。
4. 稳定的数据集 当数据集相对稳定且写操作相对较少时CopyOnWriteArrayList是一个合适的选择。写操作不频繁的情况下复制列表的开销相对较小。
常用方法
CopyOnWriteArrayList copyOnWriteArrayList new CopyOnWriteArrayList();
// 新增
copyOnWriteArrayList.add(1);
// 设置指定下标
copyOnWriteArrayList.set(0, 2);
// 获取查询
copyOnWriteArrayList.get(0);
// 删除
copyOnWriteArrayList.remove(0);
// 清空
copyOnWriteArrayList.clear();
// 是否为空
copyOnWriteArrayList.isEmpty();
// 是否包含
copyOnWriteArrayList.contains(1);
// 获取元素个数
copyOnWriteArrayList.size();
读多写少场景使用CopyOnWriteArrayList举例 假设有一个任务调度器其中包含了多个定时任务每个任务都有一个执行时间。多个线程同时读取任务列表以查看下一个要执行的任务而写入操作仅在添加、删除任务时发生。
import java.util.concurrent.CopyOnWriteArrayList;public class TaskScheduler {// 使用CopyOnWriteArrayList作为任务列表private CopyOnWriteArrayListTask taskList new CopyOnWriteArrayList();// 添加任务public void addTask(Task task) {taskList.add(task);}// 删除任务public void removeTask(Task task) {taskList.remove(task);}// 获取下一个要执行的任务public Task getNextTask() {if (!taskList.isEmpty()) {// 获取第一个任务最早执行的任务return taskList.get(0);}return null;}// 其他与任务调度相关的操作// ...
}class Task {private String name;private long executionTime;// 构造方法、getter、setter等// ...
}CopyOnWriteArrayList原理 CopyOnWriteArrayList 内部使用了一种称为“写时复制”的机制。当需要进行写操作时它会创建一个新的数组并将原始数组的内容复制到新数组中然后进行写操作。因此读操作不会被写操作阻塞读操作返回的结果可能不是最新的但是对于许多应用场景来说这是可以接受的。此外由于读操作不需要加锁因此它可以支持更高的并发度。 CopyOnWriteArrayList 的缺陷
1. 内存开销大 每次写入操作都会创建一个列表的副本这导致了较大的内存开销。在数据集很大的情况下频繁的写入操作可能占用大量内存这可能成为一个问题。2. 不适合写入频繁的场景 如果写入操作非常频繁复制列表的开销可能会变得显著并且可能会导致性能问题。适用于写多读少或写入相对较少的场景。3. 不适合实时性要求高的场景 由于写入操作会导致复制整个列表而且复制操作不是原子的因此在写入过程中读取操作可能会看到部分修改或者不一致的状态。这使得 CopyOnWriteArrayList 不适合对实时性要求很高的场景。4. 不支持并发修改迭代 CopyOnWriteArrayList 的迭代器是不可修改的因此在使用迭代器遍历列表时不允许修改列表。虽然这是为了确保线程安全设计的但对于某些应用可能会限制灵活性。5. 不适用于元素更新频繁的情况 如果在列表中的元素需要频繁更新CopyOnWriteArrayList 可能不是最佳选择因为每次更新都会导致整个列表的复制开销较大。 扩展迭代器fail-fast与fail-safe机制
fail-fast 机制 fail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时就可能会产生 fail-fast 事件。例如当某一个线程A通过iterator去遍历某集合的过程中若该集合的内容被其他线程所改变了。那么线程A访问集合时就会抛出 ConcurrentModificationException异常产生fail-fast事件。
fail-fast解决方案
方案一 在遍历过程中所有涉及到改变modCount 值的地方全部加上synchronized 或者直接使用 Collection#synchronizedList这样就可以解决问题但是不推荐因为增删造成的同步锁可能会阻塞遍历操 作。
方案二 使用CopyOnWriteArrayList 替换 ArrayList推荐使用该方案即fail-safe。
fail-safe机制 任何对集合结构的修改都会在一个复制的集合上进行因此不会抛出ConcurrentModificationException。在 java.util.concurrent 包中的集合如 CopyOnWriteArrayList、ConcurrentHashMap 等它们的迭代器一般都是采用 Fail-Safe 机制。
缺点
1. 采用 Fail-Safe 机制的集合类都是线程安全的但是它们无法保证数据的实时一致性它们只能保证数据的最终 一致性。在迭代过程中如果集合被修改了可能读取到的仍然是旧的数据。
2. Fail-Safe 机制还存在另外一个问题就是内存占用。由于这类集合一般都是通过复制来实现读写分离的因此它 们会创建出更多的对象导致占用更多的内存甚至可能引起频繁的垃圾回收严重影响性能。 ConcurrentHashMap ConcurrentHashMap是Java中线程安全的哈希表它支持高并发并且能够同时进行读写操作。 在JDK1.8之前ConcurrentHashMap使用分段锁以在保证线程安全的同时获得更大的效率。JDK1.8开始舍弃了分段锁使用自旋CASsynchronized关键字来实现同步。官方的解释中一是节省内存空间 二是分段锁需要更多的内存空间。
应用场景
1. 高并发的数据存储 当多个线程需要同时访问和修改一个共享的哈希表时ConcurrentHashMap 提供了一种线程安全的方式避免了在读取和写入操作上的锁竞争。2. 缓存管理 用作缓存的存储结构特别是在读多写少的情况下。由于 ConcurrentHashMap 具有分段锁机制多个线程可以并发地访问不同的段从而提高并发性能。3. 并发计数器 当需要进行多线程并发计数时可以使用 ConcurrentHashMap 的原子操作方法如 compute、merge 等以确保计数的一致性。4. 分布式系统中的协调 在分布式系统中ConcurrentHashMap 可以用于协调不同节点之间的数据共享。它提供了一种线程安全的方式来管理共享数据以避免并发访问问题。5. 任务调度和执行管理 在多线程的任务调度和执行场景中ConcurrentHashMap 可以用来存储和管理任务的状态、结果等信息确保在并发情况下的正确性。6. 实时性要求不高的场景 相较于传统的 HashMapConcurrentHashMap 在保证线程安全的同时提供了更高的并发性。因此在对实时性要求不是非常高的场景下可以使用它来提高并发性能。7. 在线程池中的任务管理 当多个线程池中的任务需要共享数据时ConcurrentHashMap 可以作为一个线程安全的数据结构来确保并发访问的正确性。
常用方法 // 创建一个 ConcurrentHashMap 对象
ConcurrentHashMapObject, Object concurrentHashMap new ConcurrentHashMap();
// 添加键值对
concurrentHashMap.put(key, value);
// 添加一批键值对
concurrentHashMap.putAll(new HashMap());
// 使用指定的键获取值
concurrentHashMap.get(key);
// 判定是否为空concurrentHashMap.isEmpty();
// 获取已经添加的键值对个数
concurrentHashMap.size();
// 获取已经添加的所有键的集合
concurrentHashMap.keys();
// 获取已经添加的所有值的集合
concurrentHashMap.values();
// 清空
concurrentHashMap.clear();
并发场景下线程安全举例
import java.util.concurrent.ConcurrentHashMap;public class CacheManager {// 使用ConcurrentHashMap作为缓存存储private static ConcurrentHashMapString, String cache new ConcurrentHashMap();public static void main(String[] args) {// 模拟多个线程同时访问缓存for (int i 0; i 5; i) {String key Key i;Thread readerThread new Thread(() - readFromCache(key));readerThread.start();String newKey NewKey i;String value Value i;Thread writerThread new Thread(() - addToCache(newKey, value));writerThread.start();}}// 从缓存读取数据的方法private static void readFromCache(String key) {// 使用get方法从缓存读取数据String value cache.get(key);System.out.println(Thread.currentThread().getName() : Reading from cache - Key: key , Value: value);}// 向缓存添加数据的方法private static void addToCache(String key, String value) {// 使用put方法向缓存添加数据cache.put(key, value);System.out.println(Thread.currentThread().getName() : Adding to cache - Key: key , Value: value);}
}在上述案例中多个线程可以同时读取和写入缓存项而ConcurrentHashMap会确保在多线程并发访问的情况下不会发生数据不一致或线程安全问题。
ConcurrentHashMap底层结构
在jdk1.7及其以下的版本中结构是用Segments数组 HashEntry数组 链表实现的。 jdk1.8抛弃了Segments分段锁的方案而是改用了和HashMap一样的结构操作也就是数组 链表 红黑树结构比jdk1.7中的ConcurrentHashMap提高了效率在并发方面使用了cas synchronized的方式保证数据的一致性。 链表转化为红黑树需要满足2个条件:
1. 链表的节点数量大于等于树化阈值8
2. Node数组的长度大于等于最小树化容量值64
#树化阈值为8
static final int TREEIFY_THRESHOLD 8;
#最小树化容量值为64
static final int MIN_TREEIFY_CAPACITY 64;ConcurrentSkipListMap ConcurrentSkipListMap 是 Java 中的一种线程安全、基于跳表实现的有序映射Map数据结构。 它是对 TreeMap 的并发实现支持高并发读写操作。 ConcurrentSkipListMap适用于需要高并发性能、支持有序性和区间查询的场景能够有效地提高系 统的性能和可扩展性。
应用场景
1. 并发映射 ConcurrentSkipListMap 实现了 ConcurrentMap 接口可以被用作并发映射key-value映射的数据结构。多个线程可以安全地同时访问和修改映射。2. 有序性要求 由于底层是基于跳表实现的ConcurrentSkipListMap 中的元素是有序的。如果你需要按照键的自然顺序或者自定义的比较器顺序来存储和访问数据这个类就是一个很好的选择。3. 高并发读写 跳表的结构允许多个线程并发地进行读取和写入操作而不会导致锁竞争。这使得 ConcurrentSkipListMap 在高并发环境下的读写性能表现相对较好。4. 范围查询 跳表的结构使得范围查询变得高效。ConcurrentSkipListMap 提供了一些方法如 subMap可以用于获取键范围内的子映射。5. 替代传统的并发映射实现 在某些情况下ConcurrentSkipListMap 可以作为对传统的基于锁的并发映射实现的一种替代。它的设计允许更高的并发性。
常用方法
添加元素
put(K key, V value): 将指定的键值对添加到映射中。
putIfAbsent(K key, V value): 如果指定的键尚未与值关联则将其与给定的值关联。获取元素
get(Object key): 返回指定键所映射的值如果该键不存在则返回 null。
firstKey(): 返回映射中的第一个最小的键。
lastKey(): 返回映射中的最后一个最大的键。
lowerKey(K key): 返回严格小于给定键的最大键如果不存在则返回 null。
higherKey(K key): 返回严格大于给定键的最小键如果不存在则返回 null。删除元素
remove(Object key): 从映射中删除指定键的映射。
pollFirstEntry(): 移除并返回映射中的第一个最小的映射。
pollLastEntry(): 移除并返回映射中的最后一个最大的映射。范围查询
subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive): 返回映射中键的部分视图其键范围从 fromKey 到 toKey。遍历
entrySet(): 返回包含映射中所有映射的 Set 视图。
keySet(): 返回包含映射中所有键的 NavigableSet 视图。
values(): 返回包含映射中所有值的 Collection 视图。其他操作
size(): 返回映射中的键值对数量。
isEmpty(): 如果映射不包含键值对则返回 true。
clear(): 从映射中移除所有映射。
跳表
跳表是一种基于有序链表的数据结构支持快速插入、删除、查找操作其时间复杂度为O(log n) 比普通链表的O(n)更高效。 跳表的特性
一个跳表结构由很多层数据结构组成。每一层都是一个有序的链表默认是升序。也可以自定义排序方法。 最底层链表图中所示Level1包含了所有的元素。 如果每一个元素出现在LevelN的链表中N1)那么这个元素必定在下层链表出现。 每一个节点都包含了两个指针一个指向同一级链表中的下一个元素一个指向下一层级别链表中的相同值元素。
跳表的查找 跳表的插入
跳表插入数据的流程如下
找到元素适合的插入层级K这里的K采用随机的方式。若K大于跳表的总层级那么开辟新的一层否则在对应 的层级插入。 申请新的节点。 调整对应的指针。
假设我要插入元素13原有的层级是3级假设K4