SakuraMotion 2021 - 源码分析
type: channel
link: https://yingwu2021.xyz/index.php/tag/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/
items: ConcurrentHashMap 源码分析
items: Java 常见并发容器总结
items: CopyOnWriteArrayList 简介
items: ThreadLocal 详解
items: ArrayList 源码分析

ConcurrentHashMap 源码分析
type: item
date: 2025-11-07T16:08:00+08:00
description: AI 摘要 文章对比分析了 Java 7 与 Java 8 的 ConcurrentHashMap 源码：Java 7 采用固定 16 段 Segment 加分段锁，扩容两倍，链表头插；Java 8 改用 Node 数组+链表/红黑树，以 CAS+synchronized 保证并发，扩容时容量翻倍并可能树化，get 均通过 hash 定位再遍历或树查找。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结title: ConcurrentHashMap 源码分析category: Javatag:Java集合ConcurrentHashMap 作为线程安全的 HashMap ，它的使用频率也是很高。那么它的存储结构和实现原理是怎么样的呢？1. ConcurrentHashMap 1.71. 存储结构Java 7 中 ConcurrentHashMap 的存储结构如上图，ConcurrnetHashMap 由很多个 Segment 组合，而每一个 Segment 是一个类似于 HashMap 的结构，所以每一个 HashMap 的内部可以进行扩容。但是 Segment 的个数一旦初始化就不能改变，默认 Segment 的个数是 16 个，你也可以认为 ConcurrentHashMap 默认支持最多 16 个线程并发。2. 初始化通过 ConcurrentHashMap 的无参构造探寻 ConcurrentHashMap 的初始化流程。 /** * Creates a new, empty map with a default initial capacity (16), * load factor (0.75) and concurrencyLevel (16). */ public ConcurrentHashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); }无参构造中调用了有参构造，传入了三个参数的默认值，他们的值是。 /** * 默认初始化容量 */ static final int DEFAULT_INITIAL_CAPACITY = 16; /** * 默认负载因子 */ static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * 默认并发级别 */ static final int DEFAULT_CONCURRENCY_LEVEL = 16;接着看下这个有参构造函数的内部实现逻辑。@SuppressWarnings("unchecked") public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel) { // 参数校验 if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) throw new IllegalArgumentException(); // 校验并发级别大小，大于 1<<16，重置为 65536 if (concurrencyLevel > MAX_SEGMENTS) concurrencyLevel = MAX_SEGMENTS; // Find power-of-two sizes best matching arguments // 2的多少次方 int sshift = 0; int ssize = 1; // 这个循环可以找到 concurrencyLevel 之上最近的 2的次方值 while (ssize < concurrencyLevel) { ++sshift; ssize <<= 1; } // 记录段偏移量 this.segmentShift = 32 - sshift; // 记录段掩码 this.segmentMask = ssize - 1; // 设置容量 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; // c = 容量 / ssize ，默认 16 / 16 = 1，这里是计算每个 Segment 中的类似于 HashMap 的容量 int c = initialCapacity / ssize; if (c * ssize < initialCapacity) ++c; int cap = MIN_SEGMENT_TABLE_CAPACITY; //Segment 中的类似于 HashMap…

Java 常见并发容器总结
type: item
date: 2025-11-05T09:06:00+08:00
description: AI 摘要 文章梳理了Java并发包中的核心容器：ConcurrentHashMap、CopyOnWriteArrayList、ConcurrentLinkedQueue、BlockingQueue及ConcurrentSkipListMap，分别阐述其线程安全机制、适用场景与性能特点，并对比JDK7/8实现差异，为并发编程选型提供参考。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结title: Java 常见并发容器总结category: Javatag:Java并发JDK 提供的这些容器大部分在 java.util.concurrent 包中。ConcurrentHashMap : 线程安全的 HashMapCopyOnWriteArrayList : 线程安全的 List，在读多写少的场合性能非常好，远远好于 Vector。ConcurrentLinkedQueue : 高效的并发队列，使用链表实现。可以看做一个线程安全的 LinkedList，这是一个非阻塞队列。BlockingQueue : 这是一个接口，JDK 内部通过链表、数组等方式实现了这个接口。表示阻塞队列，非常适合用于作为数据共享的通道。ConcurrentSkipListMap : 跳表的实现。这是一个 Map，使用跳表的数据结构进行快速查找。ConcurrentHashMap我们知道 HashMap 不是线程安全的，在并发场景下如果要保证一种可行的方式是使用 Collections.synchronizedMap() 方法来包装我们的 HashMap。但这是通过使用一个全局的锁来同步不同线程间的并发访问，因此会带来不可忽视的性能问题。所以就有了 HashMap 的线程安全版本—— ConcurrentHashMap 的诞生。在 JDK1.7 的时候，ConcurrentHashMap 对整个桶数组进行了分割分段(Segment，分段锁)，每一把锁只锁容器其中一部分数据（下面有示意图），多线程访问容器里不同数据段的数据，就不会存在锁竞争，提高并发访问率。到了 JDK1.8 的时候，ConcurrentHashMap 已经摒弃了 Segment 的概念，而是直接用 Node 数组+链表+红黑树的数据结构来实现，并发控制使用 synchronized 和 CAS 来操作。（JDK1.6 以后 synchronized 锁做了很多优化） 整个看起来就像是优化过且线程安全的 HashMap，虽然在 JDK1.8 中还能看到 Segment 的数据结构，但是已经简化了属性，只是为了兼容旧版本。关于 ConcurrentHashMap 的详细介绍，请看我写的这篇文章：ConcurrentHashMap 源码分析。CopyOnWriteArrayList在 JDK1.5 之前，如果想要使用并发安全的 List 只能选择 Vector。而 Vector 是一种老旧的集合，已经被淘汰。Vector 对于增删改查等方法基本都加了 synchronized，这种方式虽然能够保证同步，但这相当于对整个 Vector 加上了一把大锁，使得每个方法执行的时候都要去获得锁，导致性能非常低下。JDK1.5 引入了 Java.util.concurrent（JUC）包，其中提供了很多线程安全且并发性能良好的容器，其中唯一的线程安全 List 实现就是 CopyOnWriteArrayList 。对于大部分业务场景来说，读取操作往往是远大于写入操作的。由于读取操作不会对原有数据进行修改，因此，对于每次读取都进行加锁其实是一种资源浪费。相比之下，我们应该允许多个线程同时访问 List 的内部数据，毕竟对于读取操作来说是安全的。这种思路与 ReentrantReadWriteLock 读写锁的设计思想非常类似，即读读不互斥、读写互斥、写写互斥（只有读读不互斥）。CopyOnWriteArrayList 更进一步地实现了这一思想。为了将读操作性能发挥到极致，CopyOnWriteArrayList 中的读取操作是完全无需加锁的。更加厉害的是，写入操作也不会阻塞读取操作，只有写写才会互斥。这样一来，读操作的性能就可以大幅度提升。CopyOnWriteArrayList 线程安全的核心在于其采用了 写时复制（Copy-On-Write） 的策略，从 CopyOnWriteArrayList 的名字就能看出了。当需要修改（ add，set、remove 等操作） CopyOnWriteArrayList 的内容时，不会直接修改原数组，而是会先创建底层数组的副本，对副本数组进行修改，修改完之后再将修改后的数组赋值回去，这样就可以保证写操作不会影响读操作了。关于 CopyOnWriteArray…

CopyOnWriteArrayList 简介
type: item
date: 2025-07-23T20:05:00+08:00
description: AI 摘要 CopyOnWriteArrayList 通过“写时复制”实现线程安全，读无锁、写不阻塞读，仅写写互斥，适合读多写少场景；写操作需复制数组，内存与性能开销大，数据弱一致。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结title: CopyOnWriteArrayList 源码分析category: Javatag:Java集合CopyOnWriteArrayList 简介在 JDK1.5 之前，如果想要使用并发安全的 List 只能选择 Vector。而 Vector 是一种老旧的集合，已经被淘汰。Vector 对于增删改查等方法基本都加了 synchronized，这种方式虽然能够保证同步，但这相当于对整个 Vector 加上了一把大锁，使得每个方法执行的时候都要去获得锁，导致性能非常低下。JDK1.5 引入了 Java.util.concurrent（JUC）包，其中提供了很多线程安全且并发性能良好的容器，其中唯一的线程安全 List 实现就是 CopyOnWriteArrayList 。关于java.util.concurrent 包下常见并发容器的总结，可以看我写的这篇文章：Java 常见并发容器总结 。CopyOnWriteArrayList 到底有什么厉害之处？对于大部分业务场景来说，读取操作往往是远大于写入操作的。由于读取操作不会对原有数据进行修改，因此，对于每次读取都进行加锁其实是一种资源浪费。相比之下，我们应该允许多个线程同时访问 List 的内部数据，毕竟对于读取操作来说是安全的。这种思路与 ReentrantReadWriteLock 读写锁的设计思想非常类似，即读读不互斥、读写互斥、写写互斥（只有读读不互斥）。CopyOnWriteArrayList 更进一步地实现了这一思想。为了将读操作性能发挥到极致，CopyOnWriteArrayList 中的读取操作是完全无需加锁的。更加厉害的是，写入操作也不会阻塞读取操作，只有写写才会互斥。这样一来，读操作的性能就可以大幅度提升。CopyOnWriteArrayList 线程安全的核心在于其采用了 写时复制（Copy-On-Write） 的策略，从 CopyOnWriteArrayList 的名字就能看出了。Copy-On-Write 的思想是什么？CopyOnWriteArrayList名字中的“Copy-On-Write”即写时复制，简称 COW。下面是维基百科对 Copy-On-Write 的介绍，介绍的挺不错：写入时复制（英语：Copy-on-write，简称 COW）是一种计算机程序设计领域的优化策略。其核心思想是，如果有多个调用者（callers）同时请求相同资源（如内存或磁盘上的数据存储），他们会共同获取相同的指针指向相同的资源，直到某个调用者试图修改资源的内容时，系统才会真正复制一份专用副本（private copy）给该调用者，而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的。此作法主要的优点是如果调用者没有修改该资源，就不会有副本（private copy）被创建，因此多个调用者只是读取操作时可以共享同一份资源。这里再以 CopyOnWriteArrayList为例介绍：当需要修改（ add，set、remove 等操作） CopyOnWriteArrayList 的内容时，不会直接修改原数组，而是会先创建底层数组的副本，对副本数组进行修改，修改完之后再将修改后的数组赋值回去，这样就可以保证写操作不会影响读操作了。可以看出，写时复制机制非常适合读多写少的并发场景，能够极大地提高系统的并发性能。不过，写时复制机制并不是银弹，其依然存在一些缺点，下面列举几点：内存占用：每次写操作都需要复制一份原始数据，会占用额外的内存空间，在数据量比较大的情况下，可能会导致内存资源不足。写操作开销：每一次写操作都需要复制一份原始数据，然后再进行修改和替换，所以写操作的开销相对较大，在写入比较频繁的场景下，性能可能会受到影响。数据一致性问题：修改操作不会立即反映到最终结果中，还需要等待复制完成，这可能会导致一定的数据一致性问题。……CopyOnWriteArrayList 源码分析这里以 JDK1.8 为例，分析一下 CopyOnWriteArrayList 的底层核心源码。CopyOnWriteArrayList 的类定义如下：public class CopyOnWriteArrayList<E> extends Object implements List<E>, RandomAccess, Cloneable, Serializable { //... }CopyOnWriteArrayList 实现了以下…

ThreadLocal 详解
type: item
date: 2025-05-26T04:09:00+08:00
description: AI 摘要 文章系统剖析ThreadLocal：从弱引用key、ThreadLocalMap结构、黄金分割哈希、线性探测解决冲突，到set/get、探测式与启发式清理、扩容机制，再延伸到InheritableThreadLocal及分布式链路traceId实战，覆盖原理、源码与项目踩坑。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结title: ThreadLocal 详解category: Javatag:Java并发前言全文共 10000+字，31 张图，这篇文章同样耗费了不少的时间和精力才创作完成，原创不易，请大家点点关注+在看，感谢。对于ThreadLocal，大家的第一反应可能是很简单呀，线程的变量副本，每个线程隔离。那这里有几个问题大家可以思考一下：ThreadLocal的 key 是弱引用，那么在 ThreadLocal.get()的时候，发生GC之后，key 是否为null？ThreadLocal中ThreadLocalMap的数据结构？ThreadLocalMap的Hash 算法？ThreadLocalMap中Hash 冲突如何解决？ThreadLocalMap的扩容机制？ThreadLocalMap中过期 key 的清理机制？探测式清理和启发式清理流程？ThreadLocalMap.set()方法实现原理？ThreadLocalMap.get()方法实现原理？项目中ThreadLocal使用情况？遇到的坑？……上述的一些问题你是否都已经掌握的很清楚了呢？本文将围绕这些问题使用图文方式来剖析ThreadLocal的点点滴滴。目录注明： 本文源码基于JDK 1.8ThreadLocal代码演示我们先看下ThreadLocal使用示例：public class ThreadLocalTest { private List<String> messages = Lists.newArrayList(); public static final ThreadLocal<ThreadLocalTest> holder = ThreadLocal.withInitial(ThreadLocalTest::new); public static void add(String message) { holder.get().messages.add(message); } public static List<String> clear() { List<String> messages = holder.get().messages; holder.remove(); System.out.println("size: " + holder.get().messages.size()); return messages; } public static void main(String[] args) { ThreadLocalTest.add("一枝花算不算浪漫"); System.out.println(holder.get().messages); ThreadLocalTest.clear(); } }打印结果：[一枝花算不算浪漫] size: 0ThreadLocal对象可以提供线程局部变量，每个线程Thread拥有一份自己的副本变量，多个线程互不干扰。ThreadLocal的数据结构Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals，也就是说每个线程有一个自己的ThreadLocalMap。ThreadLocalMap有自己的独立实现，可以简单地将它的key视作ThreadLocal，value为代码中放入的值（实际上key并不是ThreadLocal本身，而是它的一个弱引用）。每个线程在往ThreadLocal里放值的时候，都会往自己的ThreadLocalMap里存，读也是以ThreadLocal作为引用，在自己的map里找对应的key，从而实现了线程隔离。ThreadLocalMap有点类似HashMap的结构，只是HashMap是由数组+链表实现的，而ThreadLocalMap中并没有链表结构。我们还要注意Entry， 它的key是ThreadLocal<?> k ，继承自WeakReference， 也就是我们常说的弱引用类型。GC 之后 key 是否为 null？回应开头的那个问题， ThreadLocal 的key是弱引用，那么在ThreadLocal.get()的时候，发生GC之后，key是否是null？为了搞清楚这个问题，我们需要搞清楚Java的四种引用类型：强引用：我们常常 new 出来的对象就是强引用类型，只要强引…

ArrayList 源码分析
type: item
date: 2024-08-03T17:59:00+08:00
description: AI 摘要 文章剖析ArrayList底层Object数组实现，详解无参构造初始为空、首次添加扩为10、后续1.5倍扩容机制，对比Vector、LinkedList，并说明System.arraycopy与Arrays.copyOf区别及ensureCapacity预扩容性能调优。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结title: ArrayList 源码分析category: Javatag:Java集合ArrayList 简介ArrayList 的底层是数组队列，相当于动态数组。与 Java 中的数组相比，它的容量能动态增长。在添加大量元素前，应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。ArrayList 继承于 AbstractList ，实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。 public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ }List : 表明它是一个列表，支持添加、删除、查找等操作，并且可以通过下标进行访问。RandomAccess ：这是一个标志接口，表明实现这个接口的 List 集合是支持 快速随机访问 的。在 ArrayList 中，我们即可以通过元素的序号快速获取元素对象，这就是快速随机访问。Cloneable ：表明它具有拷贝能力，可以进行深拷贝或浅拷贝操作。Serializable : 表明它可以进行序列化操作，也就是可以将对象转换为字节流进行持久化存储或网络传输，非常方便。ArrayList 和 Vector 的区别?（了解即可）ArrayList 是 List 的主要实现类，底层使用 Object[]存储，适用于频繁的查找工作，线程不安全 。Vector 是 List 的古老实现类，底层使用Object[] 存储，线程安全。ArrayList 可以添加 null 值吗？ArrayList 中可以存储任何类型的对象，包括 null 值。不过，不建议向ArrayList 中添加 null 值， null 值无意义，会让代码难以维护比如忘记做判空处理就会导致空指针异常。示例代码：ArrayList<String> listOfStrings = new ArrayList<>(); listOfStrings.add(null); listOfStrings.add("java"); System.out.println(listOfStrings);输出：[null, java]Arraylist 与 LinkedList 区别?是否保证线程安全： ArrayList 和 LinkedList 都是不同步的，也就是不保证线程安全；底层数据结构： ArrayList 底层使用的是 Object 数组；LinkedList 底层使用的是 双向链表 数据结构（JDK1.6 之前为循环链表，JDK1.7 取消了循环。注意双向链表和双向循环链表的区别，下面有介绍到！）插入和删除是否受元素位置的影响：ArrayList 采用数组存储，所以插入和删除元素的时间复杂度受元素位置的影响。 比如：执行add(E e)方法的时候， ArrayList 会默认在将指定的元素追加到此列表的末尾，这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插入和删除元素的话（add(int index, E element)），时间复杂度就为 O(n)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。LinkedList 采用链表存储，所以在头尾插入或者删除元素不受元素位置的影响（add(E e)、addFirst(E e)、addLast(E e)、removeFirst()、 removeLast()），时间复杂度为 O(1)，如果是要在指定位置 i 插入和删除元素的话（add(int index, E element)，remove(Object o),remove(int index)）， 时间复杂度为 O(n) ，因为需要先移动到指定位置再插入和删除。是否支持快速随机访问： LinkedList 不支持高效的随机元素访问，而 ArrayList（实现了 RandomAccess 接口） 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。内存空间占用： Array…