0%

CopyOnWrite容器:读写分离的并发容器设计与实现

CopyOnWrite(写时复制)容器是一种通过读写分离延迟更新策略实现的高效并发容器,核心思想是:读操作直接访问当前容器,写操作则复制一份新容器进行修改,完成后再替换旧容器。这种设计在 “读多写少” 的场景下能显著提升并发性能,避免读写冲突。本文以 CopyOnWriteArrayList 为例,深入解析其原理、实现及适用场景。

CopyOnWrite 核心思想

读写分离

  • 读操作:直接访问当前容器(无锁),无需阻塞;
  • 写操作:不直接修改原容器,而是复制一份新容器进行修改,修改完成后通过原子操作替换旧容器;
  • 最终一致性:写操作期间,读操作仍访问旧容器,避免 “脏读”;写操作完成后,所有新读操作会访问新容器,保证数据最终一致。

适用场景

  • 读多写少:如配置缓存、白名单列表等,读取频率远高于修改频率;
  • 允许数据短暂不一致:写操作完成前,读操作可能访问旧数据,适用于对实时性要求不高的场景;
  • 遍历操作频繁:避免迭代时的 ConcurrentModificationException(传统同步容器在迭代中修改会抛此异常)。

CopyOnWriteArrayList 源码解析

CopyOnWriteArrayList 是 List 接口的并发实现,底层通过数组存储数据,核心源码如下:

核心成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {  
// 独占锁:保证写操作的线程安全(同一时间只有一个线程执行写操作)
final transient ReentrantLock lock = new ReentrantLock();

// 存储元素的数组(volatile 保证可见性,读操作能及时看到最新容器)
private transient volatile Object[] array;

// 获取当前数组(读操作直接访问)
final Object[] getArray() {
return array;
}

// 设置新数组(写操作完成后替换旧数组)
final void setArray(Object[] a) {
array = a;
}

// 初始化:空数组
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
}
  • lock:写操作时加锁,确保同一时间只有一个线程复制和修改容器,避免多线程写操作导致的副本混乱;
  • arrayvolatile 修饰的数组,保证读操作能立即看到最新的容器替换(写操作完成后 setArray 的可见性)。

写操作:add (E e)

写操作(添加、修改、删除)的核心是复制旧数组→修改新数组→替换旧数组

阅读全文 »

使用 JOptimizer 求解线性规划(LP)问题

JOptimizer 是一个 Java 优化库,支持线性规划(LP)、二次规划(QP)等多种优化问题的求解。本文以具体示例展示如何使用 JOptimizer 解决 LP 问题,包括依赖配置、代码实现及结果解析。

问题定义

我们需要求解以下线性规划问题:
目标函数minimize 4x + 3y(最小化 4x + 3y)
约束条件

  1. 8x + 6y ≤ 25(资源约束)
  2. 3x + 4y ≥ 15(需求约束)
  3. x ≥ 0, y ≥ 0(非负约束)

环境配置

引入 JOptimizer 依赖

在 Maven 项目的pom.xml中添加依赖:

1
2
3
4
5
<dependency>
<groupId>com.joptimizer</groupId>
<artifactId>joptimizer</artifactId>
<version>5.0.0</version>
</dependency>

代码实现与解析

核心步骤

  1. 定义目标函数:构造线性目标函数4x + 3y
  2. 定义约束条件:将所有约束转化为 JOptimizer 要求的 “G·x < h” 形式。
  3. 配置优化请求:设置目标函数、约束条件等参数。
  4. 执行优化:调用 JOptimizer 的求解器,获取最优解。

完整代码

阅读全文 »

TreeMap 深度解析(基于 JDK 8)

TreeMap 是 Java 集合框架中基于红黑树实现的 Map 接口实现类,其核心特性是键的有序性—— 通过键的自然排序或定制排序维持键值对的顺序。与 HashMap 相比,TreeMap 不依赖哈希值,而是通过比较键的大小来组织数据,适用于需要有序键值对的场景。本文将从底层结构、排序机制、核心方法及红黑树平衡操作等方面,全面解析 TreeMap 的实现原理。

TreeMap 核心特性与继承关系

核心特性

  • 有序性:键值对按键的顺序(自然排序或定制排序)存储,遍历顺序为键的升序(或定制排序顺序)。
  • 无哈希冲突:基于红黑树实现,不依赖哈希值,因此不存在哈希冲突问题。
  • 键不可为 null:自然排序时,键需实现 Comparable 接口,compareTo 方法无法处理 null;定制排序时,若 Comparator 不支持 null,则键也不能为 null(否则抛出 NullPointerException)。
  • 非线程安全:多线程并发修改需手动同步(如使用 Collections.synchronizedSortedMap)。
  • 导航方法:实现 NavigableMap 接口,提供丰富的导航方法(如 ceilingKeyfloorKeysubMap 等),方便范围查询。

继承关系

TreeMap

1
2
public class TreeMap<K,V> extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable
  • 继承 AbstractMap:复用 Map 接口的基础实现(如 entrySetsize 等)。
  • 实现 NavigableMap:扩展 SortedMap 接口,支持导航操作(如获取大于 / 小于某键的最值)。
  • 实现 Cloneable:支持浅拷贝(复制红黑树结构,键值对对象本身不复制)。
  • 实现 Serializable:支持序列化,通过自定义 writeObjectreadObject 保存 / 恢复红黑树结构。

底层结构:红黑树

TreeMap 的底层是红黑树—— 一种自平衡二叉搜索树(BST),通过维护节点颜色和旋转操作,确保树的高度始终为 O(log n),从而保证增删改查的时间复杂度为 O(log n)

红黑树的基本性质

红黑树通过以下性质维持平衡:

阅读全文 »

HashMap 深度解析(基于 JDK 8)

HashMap 是 Java 集合框架中使用最广泛的 Map 实现类,基于哈希表(数组 + 链表 + 红黑树)实现,支持高效的键值对存储与查询。其设计兼顾了时间和空间效率,是单线程环境下键值对存储的首选。本文将从底层结构、核心源码、工作机制及并发问题等方面,全面剖析 HashMap 的实现原理。

HashMap 核心特性与继承关系

核心特性

  • 键唯一性:键(key)不可重复,重复插入会覆盖原值。
  • 允许 null:键和值均可为 null(键最多 1 个 null,值可多个)。
  • 无序性:存储顺序与插入顺序无关(由键的哈希值决定)。
  • 动态扩容:容量不足时自动扩容,默认扩容为原容量的 2 倍。
  • 非线程安全:多线程并发修改可能导致数据不一致(如死循环、数据覆盖)。

继承关系

HashMap

1
2
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
  • 继承 AbstractMap:复用 Map 接口的基础实现(如 entrySetkeySet 等)。
  • 实现 Map:遵循键值对集合的规范。
  • 实现 Cloneable:支持浅拷贝(仅复制结构,不复制元素对象)。
  • 实现 Serializable:支持序列化,通过 writeObjectreadObject 自定义序列化逻辑。

底层数据结构:数组 + 链表 + 红黑树

HashMap 的底层结构是哈希表,JDK 8 中采用 “数组 + 链表 + 红黑树” 的组合结构,平衡了哈希冲突处理与查询效率:

阅读全文 »

Java Map 接口及主要实现类详解

Map 是 Java 集合框架中用于存储键值对(key-value)的接口,与 Collection 接口并列,是日常开发中最常用的数据结构之一。Map 中的键(key)具有唯一性,值(value)可重复,键与值一一对应。本文将详细解析 Map 接口的主要实现类,包括 Hashtable、HashMap、TreeMap、LinkedHashMap 和 ConcurrentHashMap,分析它们的底层结构、核心特性及适用场景。

Map 接口概述

Map 接口定义了键值对集合的基本操作,如添加(put)、删除(remove)、查询(get)、遍历等。其核心方法包括:

  • V put(K key, V value):添加键值对,若键已存在则覆盖值并返回旧值。
  • V get(Object key):根据键获取值,若键不存在则返回 null
  • boolean containsKey(Object key):判断是否包含指定键。
  • Set<K> keySet():返回所有键的集合(无序,除非实现类保证有序)。
  • Collection<V> values():返回所有值的集合。
  • Set<Map.Entry<K,V>> entrySet():返回键值对实体的集合,用于遍历。

主要实现类解析

Hashtable(线程安全的早期实现)

Hashtable 是 JDK 1.0 引入的最早的 Map 实现类,基于哈希表(数组 + 链表)实现,是线程安全的,但性能较低。

核心特性
  • 线程安全:所有方法均用 synchronized 修饰,保证多线程安全,但并发效率低。

  • 不允许 null:键(key)和值(value)均不能为 null,否则抛出 NullPointerException

  • 继承关系:继承自Dictionary类(已过时),实现Map接口。

    1
    public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, Serializable
底层结构
  • 用Entry数组存储键值对,Entry是单向链表节点,包含hash、key、value、next(下一个节点)。

阅读全文 »