首页 / 资讯中心 / 文章详情

10-除了 ConcurrentHashMap,这些并发容器和阻塞队列也该会

10-除了 ConcurrentHashMap,这些并发容器和阻塞队列也该会 ★ FEATURED ARTICLE
生产者消费者模型离不了阻塞队列读多写少场景CopyOnWrite比加锁香高频插入用无锁队列更猛。这一篇把 JUC 里常用的并发容器和阻塞队列全家桶补齐让你在合适场景直接掏出对的那个而不是无脑synchronized一把锁。一、为什么不能用普通集合做并发HashMap、ArrayList都不是线程安全的多线程下HashMap并发put可能死循环JDK 7 及之前头插法扩容、数据丢失、size 错乱。ArrayList并发add可能数组越界或元素覆盖。老办法是Collections.synchronizedMap/list给每个方法包synchronized但读写全互斥并发度低。JUC 的并发容器用更聪明的策略分段锁、CAS、写时复制读写可并行。二、ConcurrentHashMap并发 Map 的标准答案JDK 8 起用「数组 链表/红黑树 CAS synchronized锁桶头」实现读几乎无锁写只锁单个桶并发度极高。注意它的复合操作不是原子的// 错误putIfAbsent 是原子的但「判断放」两句不是if(!map.containsKey(k))map.put(k,v);// 多线程下可能重复放// 正确用原子复合方法map.putIfAbsent(k,v);map.computeIfAbsent(k,key-load(key));// 存在则不计算天然防重复computeIfAbsent还顺带解决了「双重检查 初始化」的竞态是缓存懒加载的利器。三、CopyOnWriteArrayList / CopyOnWriteArraySet读多写少专属原理写时复制。修改时复制一份底层数组在新数组上改改完把引用指向新数组读永远读旧数组读写互不阻塞。CopyOnWriteArrayListStringlistnewCopyOnWriteArrayList();list.add(a);// 复制数组追加开销大list.get(0);// 直接读无锁超快适用场景极明确读远多于写如监听器列表、配置白名单、路由表。缺点也明显每次写都复制整个数组写慢、占内存存在新旧两份瞬时副本。弱一致性读到的可能是修改前的快照不适合强实时。千万别用来做「频繁 add 的队列」那就变成内存杀手。四、ConcurrentLinkedQueue高并发无锁队列基于 Michael-Scott 算法的非阻塞无锁队列用 CAS 实现入队出队高并发下比阻塞队列吞吐更高因为没有锁竞争和线程挂起。ConcurrentLinkedQueueStringqnewConcurrentLinkedQueue();q.offer(task);// 入队CAS非阻塞Stringtq.poll();// 出队空返回 null适用高并发「提交即返回」、不需要「队列空就等」的场景如日志异步收集、事件总线。它不支持阻塞等待要阻塞请用下面的阻塞队列。五、阻塞队列 BlockingQueue 全家桶BlockingQueue是生产者-消费者模型的骨架队空时取操作阻塞队满时放操作阻塞。这是线程池workQueue、ArrayBlockingQueue背后的核心接口。实现底层有界特点ArrayBlockingQueue数组有界构造必填容量单锁公平可选最常用LinkedBlockingQueue链表可选不填无界Integer.MAX双锁入队出队分离吞吐高SynchronousQueue无缓冲0 容量提交必须有人立刻接否则等线程池缓存池用它PriorityBlockingQueue堆无界按优先级出队DelayQueue堆无界按延迟时间出队到期才能取LinkedTransferQueue链表无界融合SynchronousQueue队列transfer直交典型生产者消费者BlockingQueueTaskqnewArrayBlockingQueue(100);// 生产者newThread(()-{while(true)q.put(produce());}).start();// 满则阻塞// 消费者newThread(()-{while(true)consume(q.take());}).start();// 空则阻塞注意LinkedBlockingQueue不指定容量就是无界和线程池配一起有 OOM 风险第 7 篇提过务必指定容量。几个细分用法DelayQueue实现「延迟任务调度」如订单 30 分钟未支付自动关单——元素实现Delayedtake()等到过期时间才返回。SynchronousQueue容量 0生产者put必须等消费者take接手。适合「直接交接」场景Executors.newCachedThreadPool就是它。PriorityBlockingQueue按Comparable优先级出队无界注意内存。六、ConcurrentSkipListMap / Set并发且有序需要「并发 按 key 排序」用跳表实现的ConcurrentSkipListMap对应非并发的TreeMap。它支持range视图subMap适合并发环境下的范围查询/时间序列。七、选型速查场景用什么并发 KV 缓存/映射ConcurrentHashMap读极多写极少监听器/白名单CopyOnWriteArrayList/Set高并发提交、不需阻塞ConcurrentLinkedQueue生产者-消费者要阻塞ArrayBlockingQueue有界/LinkedBlockingQueue指定容量延迟任务/定时DelayQueue直接交接SynchronousQueue并发且有序ConcurrentSkipListMap/Set八、一个总原则并发容器不是「把HashMap换成ConcurrentHashMap就完事」。想清楚三件事是否真的共享且并发读写比如何复合操作要不要原子读多写少选CopyOnWrite要阻塞选阻塞队列纯高并发入队选无锁队列K-V 选 CHM。选错比不加锁还糟——比如把CopyOnWriteArrayList当高频写队列用直接内存爆炸。总结并发容器按场景选ConcurrentHashMap解决并发 KV注意复合操作要用putIfAbsent/computeIfAbsent才原子读多写少用CopyOnWrite写时复制读写不互斥弱一致高并发非阻塞入队用ConcurrentLinkedQueue生产者-消费者用BlockingQueueArrayBlockingQueue有界最安全、DelayQueue做延迟、SynchronousQueue零缓冲交接并发有序用ConcurrentSkipListMap。LinkedBlockingQueue务必指定容量防 OOM。
阅读完成 · 觉得有帮助?
咨询建站