1. JUC并发容器概述
Java集合容器框架主要有四大类别:List、Set、Queue、Map。常见的ArrayList、LinkedList、HashMap等容器都是非线程安全的。
Java提供了同步容器(如Vector、Hashtable、SynchronizedList)通过synchronized实现同步,但会削弱并发性,降低吞吐量。为解决性能问题,java.util.concurrent包提供了多种并发类容器。
2. CopyOnWriteArrayList
2.1 概述
- 对应的非并发容器:ArrayList
- 目标:代替Vector、synchronizedList
- 原理:利用读多写少的特性,读操作不加锁,写操作时先复制新集合,修改后替换旧引用,通过volatile保证可见性
2.2 应用场景
- 读多写少的场景:读取频率远高于写入频率的缓存
- 不需要实时更新的数据:如日志缓冲批量写入
2.3 基本使用
// 创建CopyOnWriteArrayList对象
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();// 添加元素
list.add("element1");
list.add("element2");// 设置元素(指定下标)
list.set(0, "newElement");// 获取元素
String element = list.get(0);// 删除元素
list.remove(0);
list.remove("element2");// 其他操作
boolean isEmpty = list.isEmpty();
boolean contains = list.contains("element1");
int size = list.size();
list.clear();
2.4 IP黑名单判定示例
public class CopyOnWriteArrayListDemo {private static CopyOnWriteArrayList<String> blacklist = new CopyOnWriteArrayList<>();// 模拟初始黑名单数据static {blacklist.add("192.168.1.1");blacklist.add("192.168.1.2");blacklist.add("192.168.1.3");}public static void main(String[] args) {// 模拟请求处理Runnable requestHandler = () -> {try {Thread.sleep(new Random().nextInt(1000));} catch (InterruptedException e) {e.printStackTrace();}String clientIP = "192.168.1." + new Random().nextInt(6);if (blacklist.contains(clientIP)) {System.out.println(Thread.currentThread().getName() + " IP " + clientIP + " 命中黑名单,拒绝访问");return;}System.out.println(Thread.currentThread().getName() + " IP " + clientIP + " 允许访问");};// 启动多个请求线程for (int i = 1; i <= 5; i++) {new Thread(requestHandler, "请求-" + i).start();}// 黑名单更新线程new Thread(() -> {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}String newBlackIP = "192.168.1.4";blacklist.add(newBlackIP);System.out.println("系统更新: 添加新黑名单IP " + newBlackIP);}, "黑名单更新").start();}
}
2.5 实现原理
采用"写时复制"机制:
- 写操作时创建新数组,复制原始数组内容
- 在新数组上进行修改操作
- 将引用指向新数组,通过volatile保证可见性
- 读操作直接访问数组,无需加锁
2.6 缺陷
- 内存消耗:写操作需要拷贝数组,可能引发GC
- 数据一致性:不能保证实时一致性,只能保证最终一致性
- 性能问题:数据量大时写操作代价高昂
2.7 Fail-Fast vs Fail-Safe机制
Fail-Fast机制
- 特点:快速失败,检测到并发修改立即抛出ConcurrentModificationException
- 实现:java.util包中的集合类(ArrayList、HashMap等)
- 解决方案:
- 使用synchronized(不推荐,影响性能)
- 使用CopyOnWriteArrayList(推荐)
Fail-Safe机制
- 特点:安全失败,在复制的集合上修改,不抛出异常
- 实现:java.util.concurrent包中的集合类
- 缺点:
- 数据非实时一致
- 内存占用更高(需要复制)
- 可能引起频繁GC
3. ConcurrentHashMap
3.1 概述
- 对应的非并发容器:HashMap
- 目标:代替Hashtable、synchronizedMap,支持复合操作
- 原理:
- JDK6:分段锁机制
- JDK8:CAS + synchronized
3.2 应用场景
- 共享数据的线程安全:多线程环境下的数据读写
- 缓存实现:高并发缓存数据结构
3.3 基本使用
// 创建ConcurrentHashMap对象
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();// 添加键值对
map.put("key1", 1);
map.put("key2", 2);// 批量添加
HashMap<String, Integer> tempMap = new HashMap<>();
tempMap.put("key3", 3);
tempMap.put("key4", 4);
map.putAll(tempMap);// 获取值
Integer value = map.get("key1");// 特殊方法
map.putIfAbsent("key1", 100); // 不存在则put,返回null;存在返回当前值
map.remove("key1", 1); // 键值匹配才删除
map.replace("key2", 2, 20); // 键值匹配才替换// 其他操作
boolean isEmpty = map.isEmpty();
int size = map.size();
Set<String> keys = map.keySet();
Collection<Integer> values = map.values();
map.clear();
3.4 单词统计示例
public class ConcurrentHashMapDemo {private static ConcurrentHashMap<String, AtomicLong> wordCountMap = new ConcurrentHashMap<>();private static CountDownLatch latch = new CountDownLatch(3);private static String[] words = {"apple", "banana", "orange", "apple", "banana"};public static void main(String[] args) throws InterruptedException {Runnable counterTask = () -> {for (int i = 0; i < 5; i++) {String word = words[new Random().nextInt(words.length)];// 获取当前计数,不存在则初始化AtomicLong count = wordCountMap.get(word);if (count == null) {AtomicLong newCount = new AtomicLong(0);count = wordCountMap.putIfAbsent(word, newCount);if (count == null) {count = newCount;}}// 增加计数count.incrementAndGet();System.out.println(Thread.currentThread().getName() + ": " + word + " 计数: " + count.get());}latch.countDown();};// 启动多个计数线程for (int i = 1; i <= 3; i++) {new Thread(counterTask, "计数器-" + i).start();}latch.await();System.out.println("最终统计结果: " + wordCountMap);}
}
3.5 数据结构演进
HashTable结构
- 全表锁,性能低下
JDK1.7 ConcurrentHashMap
- 结构:Segment数组 + HashEntry数组 + 链表
- 机制:分段锁,写操作分散到不同段
JDK1.8+ ConcurrentHashMap
- 结构:数组 + 链表 + 红黑树(同HashMap)
- 机制:CAS + synchronized
- 树化条件:
- 链表节点数 ≥ 8(TREEIFY_THRESHOLD)
- 数组长度 ≥ 64(MIN_TREEIFY_CAPACITY)
4. ConcurrentSkipListMap
4.1 概述
- 对应的非并发容器:TreeMap
- 特点:基于跳表实现的线程安全有序Map
- 优势:支持高并发有序访问和区间查询
4.2 跳表(Skip List)原理
跳表是基于有序链表的概率型数据结构,支持O(log n)时间复杂度的查找、插入、删除操作。
跳表特性
- 多层链表结构组成
- 每层都是有序链表
- 最底层包含所有元素
- 高层元素必定在低层出现
- 节点包含两个指针:同级下一个元素、下层相同值元素
跳表操作
- 查找:从最高层开始,向右查找直到大于目标值,然后向下一层继续
- 插入:
- 随机确定插入层级K
- K大于当前层级时创建新层
- 申请新节点并调整指针
4.3 基本使用
public class ConcurrentSkipListMapDemo {public static void main(String[] args) {ConcurrentSkipListMap<Integer, String> skipListMap = new ConcurrentSkipListMap<>();// 添加元素(自动排序)skipListMap.put(3, "Value3");skipListMap.put(1, "Value1");skipListMap.put(4, "Value4");skipListMap.put(2, "Value2");// 获取元素String value = skipListMap.get(2);System.out.println("Key=2的值: " + value);// 遍历元素(有序)System.out.println("按顺序遍历:");for (Integer key : skipListMap.keySet()) {System.out.println(key + " : " + skipListMap.get(key));}// 范围查询System.out.println("Key在1-3之间的元素:");ConcurrentNavigableMap<Integer, String> subMap = skipListMap.subMap(1, true, 3, true);subMap.forEach((k, v) -> System.out.println(k + " : " + v));// 删除元素String removedValue = skipListMap.remove(3);System.out.println("删除的值: " + removedValue);}
}
5. 其他并发容器(部分不常用)
5.1 CopyOnWriteArraySet
- 对应的非并发容器:HashSet
- 原理:基于CopyOnWriteArrayList实现
- 特点:使用addIfAbsent方法保证元素唯一性
5.2 并发Queue
- ArrayBlockingQueue:数组实现的有界阻塞队列
- LinkedBlockingQueue:链表实现的可选有界队列
- ConcurrentLinkedQueue:高性能非阻塞队列
- PriorityBlockingQueue:支持优先级的无界阻塞队列
5.3 并发Deque
- ConcurrentLinkedDeque:并发双端队列
- LinkedBlockingDeque:链表实现的双端阻塞队列
6. 性能考量与最佳实践
6.1 性能影响因素
- 并发级别:根据实际并发访问量选择合适容器
- 读写比例:读多写少选CopyOnWrite,写多选ConcurrentHashMap
- 数据量大小:大数据量考虑ConcurrentSkipListMap
- 一致性要求:强一致选Hashtable,弱一致选并发容器
6.2 最佳实践
- 明确需求:根据业务场景选择最合适的容器
- 性能测试:在实际负载下测试容器性能
- 监控GC:关注并发容器可能引起的内存和GC问题
- 避免过度设计:简单场景使用简单解决方案
// 容器选型决策示例
public class ContainerSelector {public static <K, V> Map<K, V> createMap(boolean needOrdering, int expectedSize, int concurrencyLevel) {if (needOrdering) {return new ConcurrentSkipListMap<>();} else if (expectedSize > 1000000 || concurrencyLevel > 100) {return new ConcurrentHashMap<>(expectedSize, 0.75f, concurrencyLevel);} else {return new ConcurrentHashMap<>();}}public static <E> List<E> createList(boolean readHeavy, int expectedSize) {if (readHeavy && expectedSize < 10000) {return new CopyOnWriteArrayList<>();} else {return Collections.synchronizedList(new ArrayList<>());}}
}
选型总结
场景特点 | 推荐容器 | 理由 |
---|---|---|
键值对操作,高并发 | ConcurrentHashMap | 线程安全,性能优良 |
大数据量有序访问 | ConcurrentSkipListMap | 跳表结构,高效增删 |
读多写少,数据量小 | CopyOnWriteArrayList | 读无锁,写时复制 |
强一致性要求 | Hashtable | 全表锁,保证强一致 |