java数据结构:ConcurrentSkipListSet<E>与CopyOnWriteArraySet<E>

本文深入探讨了Java并发集合中的ConcurrentSkipListSet与CopyOnWriteArraySet类,包括它们的特性和用法。ConcurrentSkipListSet是一个基于SkipList的数据结构,支持线程安全的并发操作,适用于需要自然排序的集合。CopyOnWriteArraySet则是线程安全的数组实现,适合小规模集合且读操作远多于写操作的场景。文章详细对比了两者的特点,帮助开发者在不同需求下选择合适的集合类。

 


分类: java_数据结构 java_线程   313人阅读  评论(0)  收藏  举报
[java]  view plain copy
  1. import java.util.concurrent.ConcurrentSkipListSet;  
  2. import java.util.concurrent.ExecutorService;  
  3. import java.util.concurrent.Executors;  
  4.   
  5. import org.junit.Test;  
  6.   
  7. /** 
  8.  * @author amber2012 
  9.  *  
  10.  * 1、ConcurrentSkipListSet<E>: 
  11.  *      public class ConcurrentSkipListSet<E> extends AbstractSet<E>  
  12.  *                      implements NavigableSet<E>, Cloneable, Serializable 
  13.  *  
  14.  *  关于ConcurrentSkipListSet<E>在jdk的API的文档说明: 
  15.  *      1)ConcurrentSkipListSet<E>是jdk6新增的类,位于java.util.concurrent并发库下; 
  16.  *  
  17.  *      2)ConcurrentSkipListSet<E>和TreeSet一样,都是支持自然排序,并且可以在构造的时候定义Comparator<E> 
  18.  *      的比较器,该类的方法基本和TreeSet中方法一样(方法签名一样); 
  19.  *  
  20.  *      3)和其他的Set集合一样,ConcurrentSkipListSet<E>都是基于Map集合的,ConcurrentSkipListMap便是它的底层实现; 
  21.  *  
  22.  *      4)在多线程的环境下,ConcurrentSkipListSet<E>中的contains、add、remove操作是安全的,多个线程可以安全地并发 
  23.  *      执行插入、移除和访问操作。但是对于批量操作 addAll、removeAll、retainAll 和 containsAll并不能保证以原子方式执行, 
  24.  *      理由很简单,因为addAll、removeAll、retainAll底层调用的还是contains、add、remove的方法,在批量操作时,只能保证 
  25.  *      每一次的contains、add、remove的操作是原子性的(即在进行contains、add、remove三个操作时,不会被其他线程打断),而 
  26.  *      不能保证每一次批量的操作都不会被其他线程打断。 
  27.  *       
  28.  *      5)此类不允许使用 null 元素,因为无法可靠地将 null 参数及返回值与不存在的元素区分开来。  
  29.  *  
  30.  * 2、public class CopyOnWriteArraySet<E>extends AbstractSet<E>implements Serializable 
  31.  *      对于CopyOnWriteArraySet<E>类: 
  32.  *      1)它最适合于具有以下特征的应用程序:set 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突。  
  33.  *      2)它是线程安全的, 底层的实现是CopyOnWriteArrayList;   
  34.  *      3)因为通常需要复制整个基础数组,所以可变操作(add、set 和 remove 等等)的开销很大。  
  35.  *      4)迭代器不支持可变 remove 操作。  
  36.  *      5)使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照。  
  37.  *  
  38.  */  
  39. public class ConcurrentSkipListSetTest {  
  40.       
  41.     private static ConcurrentSkipListSet<Student> listSet = new ConcurrentSkipListSet<Student>();  
  42.       
  43.     static {  
  44.         for(int i = 1; i <= 30; i++){  
  45.             listSet.add(new Student(new Long(i)));  
  46.         }  
  47.     }  
  48.       
  49.     public static void main(String[] args) {  
  50.           
  51.         ExecutorService threadPool = Executors.newFixedThreadPool(3);  
  52.         threadPool.execute(new Runnable() {  
  53.             public void run() {  
  54.                 while(listSet.size() != 0){  
  55.                     sop(Thread.currentThread().getName()+ " : " + listSet.pollFirst()); //获取并移除第一个(最低)元素  
  56.                 }  
  57.             }  
  58.         });  
  59.           
  60.         threadPool.execute(new Runnable() {  
  61.             public void run() {  
  62.                 while(listSet.size() != 0){  
  63.                     sop(Thread.currentThread().getName()+ " : " + listSet.pollLast()); //获取并移除最后(最高)元素  
  64.                 }  
  65.             }  
  66.         });  
  67.           
  68.         threadPool.execute(new Runnable() {  
  69.             public void run() {  
  70.                 while(listSet.size() != 0){  
  71.                     sop(Thread.currentThread().getName()+ " : " + listSet.pollFirst()); //获取并移除第一个(最低)元素  
  72.                 }  
  73.             }  
  74.         });  
  75.     }  
  76.       
  77.     @Test  
  78.     public void test(){  
  79.     }  
  80.       
  81.     private static void sop(Object obj){  
  82.         System.out.println(obj);  
  83.     }  
  84. }  
<think>我们正在讨论Java并发包中的并发容器,特别是它们的数据结构和实现原理。根据之前的讨论和引用内容,我们可以将并发容器分为List、Set、Map和Queue等几类,并分别探讨其内部实现机制。 注意:由于用户要求详细解析数据结构和实现原理,我们将重点放在每个容器的内部实现上,包括使用的数据结构、同步机制(如CAS、锁、写时复制等)以及关键操作的流程。 我们将按照以下结构进行: 1. 线程安全List:CopyOnWriteArrayList 2. 线程安全Set:CopyOnWriteArraySet, ConcurrentSkipListSet 3. 线程安全Map:ConcurrentHashMap, ConcurrentSkipListMap 4. 线程安全队列:由于之前已经讨论过,这里简要提及,重点在非阻塞队列ConcurrentLinkedQueue和阻塞队列的原理 注意:引用内容中提到了LongAdder(空间换时间,多段锁理念)和Semaphore(基于AQS)等,但这些不属于容器,因此不在本问题讨论范围内。 下面开始详细解析:</think>### Java 并发容器:数据结构实现原理详解 Java 并发包(`java.util.concurrent`)通过创新设计平衡线程安全性能,核心容器采用独特数据结构同步机制。以下是关键容器的实现原理分析: --- #### 一、`CopyOnWriteArrayList`(写时复制列表) **数据结构**: - 底层使用 `volatile Object[] array` 存储数据 - 写操作时复制新数组($O(n)$),更新引用指向新数组 **实现原理**: ```java // JDK 源码核心逻辑 public boolean add(E e) { synchronized(lock) { Object[] es = getArray(); int len = es.length; Object[] newElements = Arrays.copyOf(es, len + 1); // 复制新数组 newElements[len] = e; setArray(newElements); // volatile 写保证可见性 return true; } } ``` **同步机制**: - 写操作:互斥锁(JDK15+ 使用 `synchronized`,早期用 `ReentrantLock`) - 读操作:无锁直接访问数组(依赖 `volatile` 保证可见性) - **适用场景**:监听器列表、配置管理等**读多写少**场景(写频率 < 1%)[^1] --- #### 二、`ConcurrentHashMap`(高并发哈希表) **数据结构演进**: | JDK版本 | 数据结构 | 锁机制 | |---------|-------------------|-----------------------| | JDK7 | 分段数组 + 链表 | 分段锁(`ReentrantLock`)| | JDK8+ | 数组 + 链表/红黑树 | CAS + `synchronized` | **JDK8 实现原理**: 1. **初始化**:延迟加载,首次插入时初始化数组 2. **插入操作**: ```java final V putVal(K key, V value, boolean onlyIfAbsent) { if (key == null || value == null) throw new NullPointerException(); int hash = spread(key.hashCode()); int binCount = 0; for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; if (tab == null || (n = tab.length) == 0) tab = initTable(); // 懒初始化 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value))) break; // CAS 插入空桶 } else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); // 协助扩容 else { synchronized (f) { // 锁住桶头节点 if (tabAt(tab, i) == f) { if (fh >= 0) { // 链表插入 } else if (f instanceof TreeBin) { // 红黑树插入 } } } } } } ``` 3. **扩容机制**: - 多线程协同扩容(通过 `ForwardingNode` 标记迁移状态) - 扩容期间查询可访问新旧两表 **关键优化**: - **链表转红黑树**:桶节点数 ≥ 8 时转换(退化阈值=6) - **计数器分离**:采用 `LongAdder` 思想实现 `CounterCell[]` 分散计数[^3] --- #### 三、`ConcurrentSkipListMap`(跳表映射) **数据结构**: - 多层跳表结构(空间复杂度 $O(n)$,查询复杂度 $O(\log n)$) - 节点结构: ```java static final class Node<K,V> { final K key; volatile Object value; volatile Node<K,V> next; volatile Index<K,V> down; // 下层指针 } ``` **实现原理**: 1. **查询**:从顶层开始向右/向下搜索(无锁) 2. **插入**: - CAS 创建新节点 - 随机生成层级($P(level=k) = \frac{1}{2^k}$) - 自顶向下构建索引链(CAS 更新指针) 3. **删除**:标记节点逻辑删除 + CAS 更新指针 **优势**: - 相比平衡树:无全局锁,范围查询高效 - 天然有序(支持 `ConcurrentNavigableMap`) --- #### 四、非阻塞队列 **`ConcurrentLinkedQueue` 实现原理**: - **数据结构**:单向链表 + CAS ```java private transient volatile Node<E> head; private transient volatile Node<E> tail; ``` - **入队操作**: 1. CAS 修改 `tail.next` 2. CAS 更新 `tail` 指针(可能延迟更新) - **出队操作**: 1. CAS 设置头节点 `item=null`(逻辑删除) 2. CAS 更新 `head` 指针 **关键特性**: - 无界队列,依赖 CAS 保证线程安全 - `size()` 非精确(遍历计数) --- ### 设计思想对比 | 容器类型 | 核心思想 | 数据结构 | 适用场景 | |------------------------|------------------|-------------------|-----------------------| | `CopyOnWriteArrayList` | 读写分离 | 动态数组 | 读多写少(配置管理) | | `ConcurrentHashMap` | 锁粒度最小化 | 哈希表+红黑树 | 高频K-V访问(缓存) | | `ConcurrentSkipListMap`| 概率平衡 | 多层跳表 | 有序并发访问(排行榜)| | `ConcurrentLinkedQueue`| 无锁算法 | 单向链表 | 高吞吐生产消费 | --- ### 性能优化实践 1. **避免复合操作**: ```java // 错误:非原子操作 if (!map.containsKey(k)) map.put(k, v); // 正确:使用原子方法 map.computeIfAbsent(k, key -> new AtomicLong()); ``` 2. **控制写时复制频率**: ```java // 批量写入减少复制次数 list.addAll(Arrays.asList(data1, data2, data3)); ``` 3. **利用有序容器特性** ```java // 高效范围查询(跳表) map.subMap("2023-01", "2023-12").forEach(...); ``` --- ### 总结 Java 并发容器的实现核心: 1. **降低冲突**:通过分段(JDK7 HashMap)、分散计数(`CounterCell`)、跳表层级等减少竞争 2. **无锁化**:CAS 实现非阻塞操作(如 `ConcurrentLinkedQueue`) 3. **读写分离**:写时复制牺牲写性能换取读并发(`CopyOnWriteArrayList`) 4. **结构优化**:链表转红黑树(哈希冲突)、跳表替代平衡树(范围查询) > **设计启示**: > - 读多写少场景优先写时复制 > - 高频更新场景选择锁粒度更小的 `ConcurrentHashMap` > - 有序需求使用跳表结构 > - 生产消费模型匹配队列特性(阻塞/非阻塞)[^2] ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值