List、Set、Map
记一次ListString转换SetString的代码实现及其扩展。ListString list Arrays.asList(aaa,bbb,ccc); //第一种方式 SetString set list.stream().collect(Collectors.toSet()) //第二种方式 SetString set2 new HashSet(); if(list!null){ set2.addAll(list); }一、List vs Set对比特性ListSet查找性能contains()是O(n)需要遍历contains()是O(1)哈希查找有序性保持插入顺序HaseSet不保证顺序重复元素允许重复自动去重内存占用相对较小相对较大哈希表结构使用Set的优缺点相对于List优点查找效率高contains()是O(1)List是O(n)自动去重防御性编程避免脏数据导致逻辑异常缺点失去了原始数据的顺序信息依据业务选择如果不在乎顺序则可以用Set多占用一些内存若数据量可控则性能收益更大二、HashMap vs HashSet对比特性HashMapHashSet存储内容键值对(Key-Value)仅存储元素(Key)底层实现数组链表/红黑树内部封装了一个HashMap用途需要通过Key查找Value仅需判断元素是否存在内存占用较大存储KV相对较小(只存KV是一个固定的Object)常用方法put(),get(),containsKey()add(),contains()是否允许null允许一个null key多个null value允许一个null元素2.1选择HashSet的优点语义清晰contains()直接表达“是否包含”的意图代码简洁不需要维护无意义的value内存更省HashSet内部虽然也是用HashMap实现但value统一是同一个PERSENT静态对象比每次存Boolean更省内存仅判断存在性的需求Set更适合总结场景推荐选择仅需判断元素是否存在HashSet需要通过key找到对应valueHashMap需要去重并保持顺序LinkedHashSet需要排序TreeSet/TreeMap三、线程安全ConcurrentHashMap.newKeySet() 推荐SetString set ConcurrentHashMap.newKeySet();基于ConcurrentHashMap的newKeySet线程安全性能高分段锁最推荐用于高并发场景Collections.synchronizedSetSetString set Collections.synchronizedSet(new HashSet());包装器模式对所有方法加synchronized线程安全但并发性能较低全局锁CopyOnWriteArraySetSetString set new CopyOnWriteArraySet();写时复制读操作无锁适合读多写少的场景写入开销大需要复制整个集合ConcurrentHashMap代替如果需要更多操作ConcurrentHashMapString ,Boolean map new ConcurrentHashMap();判断存在性用containsKey 或 putIfAbsent对比总结实现方式线程安全机制适用场景性能特点ConcurrentHashMap.newKeySet分段锁高并发读写读写性能都高Collections.synchronizedSet全局锁低并发并发度低CopyOnWriteArraySet写时复制读多写少读极快写极慢6. 不需要线程安全的情况方法内的局部变量没有多线程共享访问单线程内完成构建和使用如果作为类成员变量或缓存被多线程访问才需要用线程安全的方式四、ConcurrentHashMap分段锁4.1什么是分段锁Segment Lock分段锁是ConcurrentHashMap的核心并发机制它将整个哈希表分成多个独立的段(Segment)每个段时一把独立的锁。传统HashTable全局锁[整个哈希表] -所有操作串行ConcurrentHashMap分段1|分段2|分段3|...锁A|锁B|锁C|...不同分段的操作可以并行执行4.2JDK7 vs JDK8的实现差异版本分段锁实现段数量JDK7显式Segment类继承ReentrantLock默认16个JDK8取消Segment直接用synchronizedCAS更细粒度桶级别JDK8的优化锁的粒度从“段”细化到“单个桶链表头节点”并发度更高4.3为什么适合高并发1.并发度提升假设默认16个分段理想情况下16个线程可以同时操作不同分段的数据对比HashTable1个线程能操作Collections.synchronizedMap1个线程能操作ConcurrentHashMap16个线程能同时操作2.读操作基本无锁//get操作不需要加锁使用volatile保证可见性 public V get(Object key){ NodeK,V[] tab;NodeK,V e,p; int n,eh;K ek; int h spread(key.hashCode()); //直接定位桶volatile读取 if((tabtable)!null (ntab.length)0 (etabAt(tab,(n-1) h))!null){ // ... 遍历链表或红黑树 } return null; }3.写操作细粒度加锁//put操作只锁住对应的桶链表头节点 final V putVal(K key,V value,boolean onlyIfAbsent){ //计算hash定位到具体桶 int hash spread(key.hashCode()); int binCount 0; for(NodeK,V[] tab table;;){ NodeK,V f;int n,i,fhl //... //只锁住这个桶的头节点 f synchronized (f) { //在这个桶内操作插入、链表转红黑树等 } } }4.4核心优化技术技术作用CAS无锁初始化、计数等操作volatile保证数组和节点的可见性synchronized(细粒度)只锁单个桶而非整个表红黑树链表过长时转为树O(n) - O(log n)ForwardingNode扩容时线程可以协助迁移数据4.5对比其他线程安全Map实现锁机制并发度适用场景HashTable全局synchronized低已淘汰Collections.synchronizedMap全局synchronized低简单场景ConcurrentHashMap分段/桶级锁高高并发读写ConcurrentSkipListMap无锁(CAS)高需要排序4.6总结分段锁的核心思想时“锁细化”将“一把大锁”拆成“多把小锁”不同分段的数据操作互不干扰读基本无锁写只锁局部从而实现高并发下的高性能五、HashMap vs ConcurrentHashMap对比特性HashMapConcurrentHashMap线程安全非线程安全线程安全锁机制无锁分段锁JDK7/桶级锁JDK8性能单线程下最优高并发下最优null支持允许null key和null value不允许null key和null value迭代器fail-fast快速失败弱一致性不会抛ConcurrentModificationException适用场景单线程、局部变量多线程共享、高并发内存占用较小略大维护并发控制结构核心差异5.1线程安全形//HashMap - 多线程下会出问题 MapString,Object map new HashMap(); //并发put 可能导致数据丢失、死循环JDK7、size不准确 //ConcurrentHashMap - 线程安全 MapString,Object cmap new ConcurrentHashMap(); //并发操作安全数据一致性保证5.2锁粒度对比集合锁HashMap无锁HashTable全局锁所有操作串行Collections.synchronizedMap全局锁所有操作串行ConcurrentHashMap[桶1锁][桶2锁][桶3锁]不同桶的操作可以并行16线程可同时操作5.3null值策略HashMapConcurrentHashMapnull key允许1个不允许抛NullPointerExceptionnull value允许多个不允许抛NullPointerException为什么ConcurrentHashMap不允许null//并发场景下无法区分“key 不存在”和“key存在但value为null”get(key) 返回nll时语义模糊concurrentHashMap.get(key);//返回null,是key不存在还是value就是null5.4迭代器行为//HashMap - fail-fast并发修改会抛异常 MapString,String hashMap new HashMap(); hashMap.put(a,1); IteratorString it hashMap.keySet().iterator(); hashMap.put(b,2);//修改了map it.next();//抛出ConcurrentModificationException //ConcurrentHashMap - 弱一致性不会抛异常 MapString,String cMap new ConcurrentHashMap(); cMap.put(a,1); IteratorString it2 cMap.keySet().iterator(); cMap.put(b,2);//修改了map it2.next();//正常执行可能读到也可能读不到“b”5.5性能对比场景HashMapConcurrentHashMap单线程put/get最优略慢有额外并发控制开销多线程并发读不安全极高读基本无锁多线程并发写不安全高分段锁并行写入混合读写不安全高5.6选择建议场景推荐选择方法内局部变量单线程使用HashMap类成员变量多线程共享ConcurrentHashMap需要排序TreeMap/ConcurrentSkipListMap需要缓存过期策略Caffeine/Guava Cache