并发基础与 JMM 入门

  • 并发基础与 JMM 入门
    • 并发三大问题
      • 原子性
        • 定义:一个操作要么全部完成,要么完全不完成,中间过程对其他线程不可见
        • 典型反例:i++ 不是原子操作
      • 可见性
        • 定义:一个线程对共享变量的修改,能否及时被其他线程看到
        • 根因
          • CPU 缓存(L1/L2/L3) 与编译器/CPU 重排序
          • 线程可能一直读到自己缓存中的旧值
        • 解决方法
          • volatile: 保证写入对其他线程可见
          • synchronized/Lock:解锁前的写对之后加锁的读可见
          • final:初始化安全
      • 有序性
        • 定义:代码执行顺序是否一定和你写的一样
        • 根因
          • 编译器优化:指令重排(JIT)
          • CPU 乱序执行
    • JMM(Java 内存模型)
      • JMM 解决的问题
        • JMM 的目标不是描述硬件,而是给 Java 程序提供一个跨平台一致的并发可见性与有序性规范
        • 规范:
          • 写的代码跑在不同 CPU 上,缓存协议,重排序规则不同
          • JMM 通过 happens-before 规定:只要满足 HB,结果必须可见且有序;不满足 HB,结果运行不可预测
          • HB = Java 并发正确性的法律条文
      • happends-before (HB)
        • HB 是可见性 + 有序性的保证关系
        • A happens-before B
          • A 的结果对 B 可见
          • A 的执行顺序对 B 先行约束
        • 没有 HB,就不保证可见/有序
      • happends-before 规则
        • 程序次序规则
          • 同一个线程能,按程序顺序,前面的操作 HB 后面的操作
        • 监视器锁规则
          • 对同一把锁,unlock HB 之后对同一锁的 lock
        • volatile 变量规则
          • 对同一 volatile 变量,对 volatile 的写 HB 后续对该 volatile 的读
        • 线程启动规则
          • Thread.start() HB 该线程内的所有操作
        • 线程终止规则
          • 线程内所有操作 HB 其他线程成功从 Thread.join() 返回
        • 中断规则
          • 对线程调用 interrupt() HB 被中断线程检测到中断
        • 传递性
          • 若 A HB B,B HB C,则 A HB C
    • volatile
      • volatile 保证什么
        • 可见性:写入会刷新到主内存,读取会从主内存获取
        • 有序性:禁止 volatile 写/读周围的特定重排序(内存屏障)
        • 建立 happens-before:写 HB 读
      • volatile 不保证
        • 不保证复合操作原子性
        • 不保证所有代码不重拍:只约束与 volatile 相关的重排边界
      • volatile 的经典使用场景
        • 状态标志:停止线程 volatile boolean stop
        • 单例的安全发布
        • 读多写少的配置刷新
    • DLC(双重检查锁)为什么必须加 volatile
      • new Singleton() 在底层上被视为三步:
        • 分配内存
        • 调用构造器初始化对象
        • 把引用赋给 instance
      • 如果发生重排序,可能变为
        • 分配内存
        • 赋引用给 instance
        • 初始化对象
      • 另一个线程看到 instance != null 就返回,但是拿到半初始化对象
      • volatile 禁止这种关键重排并建立 HB,确保安全发布
    • final 的初始化安全
      • final 字段在构造完成后有更强的可见性保障

线程模型与线程安全

  • 线程模型与线程安全
    • Java 线程模型:Thread/Runnable/Callable/Future
      • 四者关系与使用场景
        • Thread: 线程载体+执行单元
        • Runnable:无返回值、不能抛受检异常
        • Callable: 有返回值,可抛异常(配合 Future)
        • Future:异步结果句柄(拿结果、取消、超时)
        • Thread 是执行容器,Runnable/Callable 是任务;Callable 比 Runnable 多返回值与异常;Future 是任务的控制面板
      • Future API
        • get():阻塞拿结果
        • get(timeout, unit):超时等待
        • cancel(true/false):尝试取消
          • true:会向线程发 interrupt
          • false:不 interrupt,只是标记取消
        • isDone()/isCancelled()
    • 线程生命周期与状态
      • Java Thread.State
        • NEW:未 start
        • RUNNABLE:可运行(包括运行中+就绪)
        • BLOCKED:等待获取 monitor 锁(synchronized 竞争)
        • WAITING:无限期等待(Object.wait()/Thread.join()/LockSupport.park())
        • TIME_WAITED:限时等待(sleep()/wait(timeout)/join(timeout)/parkNanos)
        • TERMINATED:结束
      • BLOCKED 和 WAITING 的区别
        • BLOCKED:卡在 synchronized,等锁
        • WAITING:已经拿到锁,主动等待某个条件
      • 线程切换成本
        • 上下文切换:保存恢复寄存器、栈、调度
        • CPU cache 命中下降
        • 线程越多,竞争越激烈,吞吐反而下降
    • 中断机制:interrupt 不是杀进程
      • 三个 API 的语义
        • thread.interrupt():给线程设置中断标志位
        • thread.isInterrupted():读取中断标志位(不清除)
        • Thread.interrupted():读取当前线程的中断标志位,并且清除
      • 那些操作对中断敏感
        • 会抛 InterruptedException (清除中断标志)
          • Object.wait()
          • Thread.sleep()
          • Thread.join()
          • BlockingQueue.put/take 等可中断阻塞
        • Lock 的可中断
          • lockInterruptibly():阻塞等锁时可响应 interrupt
          • lock():等锁期间不相应 interrupt
      • 优雅停线程
        • 循环里检查中断标志
        • 捕获 InterruptedException 后要么退出,要么重新设置中断标志位再退出/上抛
    • 线程安全方法论
      • 不变性
        • 对象状态创建后不再改变
        • 示例: String、Interger
        • 业务做法:配置对象、DTO 用不可变 + 构造完成后安全发布
        • 优点:天然线程安全、无锁高性能
        • 缺点:需要复制,内存开销上升
      • 线程封闭
        • 数据只在一个线程访问
        • 典型:方法栈内局部变量、线程私有变量
        • 示例:每个线程一个 SimpleDateFormat
      • 同步互斥
        • 通过临界区序列化访问共享状态
      • 无锁/低锁
        • 原子类/CAS、分段化
    • Synchronized
      • Synchronized 锁的是什么
        • synchronized(obj):锁 obj 的 monitor
        • synchronized 实例方法:锁 this
        • static synchronized: 锁 Class 对象(xxx.class)
        • 锁的是对象的 monitor,不是代码块本身
      • synchornized 三大语义
        • 互斥:同一时刻只有一个线程进入临界区
        • 可见性:释放锁前的写,对之后获取同一锁的线程可见
        • 可重入:同一线程可重复获得同一把锁
      • 锁升级
        • 目标:在无竞争/低竞争时,让 synchronzied 尽可能便宜;在竞争激烈时保证正确性与吞吐。
        • 策略
          • 无竞争:尽量不做原子指令/不进内核
          • 少量竞争:用 CAS + 自旋解决(避免线程阻塞/唤醒成本)
          • 激烈竞争:进入 monitor,阻塞/唤醒
        • 对象头与 Mark Word:锁状态存在哪里
          • 每个 Java 对象在 HotSpot 里都有对象头,核心是 Mark Word,用于存:
            • 哈希码
            • GC 分代/标记信息
            • 锁相关信息(锁标志位、线程 ID、指针等)
          • synchronized 不单独存一个锁对象结构,优先把锁状态编码在对象头里;必要时才膨胀到 monitor(重量级)。
        • 四种主要锁形态
          • 无锁
            • 对象头处于可锁定但未加锁状态
            • 第一次进入 synchronized,会走向偏向/轻量的获取逻辑
          • 偏向锁
            • 适用场景:几乎总是同一个线程反复进入同一把锁
            • 核心思想:预期每次都 CAS/自旋,不如直接偏向某个线程
            • MarkWord 记录偏向的线程 id
            • 同一线程再次进入,只需要快速检查是不是我,几乎零成本
            • 偏向锁的获取:
              • 第一次进入:尝试把对象头偏向当前线程(轻量 CAS 一次)
              • 再次进入:只做线程 ID 校验
            • 偏向锁的撤销
              • 另一个线程也来竞争这把锁
              • 偏向线程已经结束,且有其他线程来加锁
              • JVM 需要批量撤销/重偏向
          • 轻量级锁
            • 适用场景:有短暂竞争,但竞争不激烈;临界区很短
            • 核心机制
              • 线程在栈上创建一个 Lock Record(锁记录)
              • 把对象头的 Mark Word 复制到 Lock Record(保存旧值)
              • 用 CAS 尝试把对象头替换为指向 Lock Record 的指针(同时设置轻量锁标志)
                • CAS 成功:拿到轻量锁
                • CAS 失败:说明有竞争 -> 可能自旋等待 -> 若仍失败/竞争升高 -> 膨胀为重量级锁
          • 重量级锁
            • 使用场景:竞争激烈,自旋会浪费 CPU,或者线程阻塞更划算
            • 核心机制
              • 对象关联一个 ObjectMonitor
              • 竞争失败的线程进入 EntryList/WaitSet(不同状态的队列)
              • 由 OS 互斥/park-unpark 等机制进行阻塞与唤醒
            • 特点
              • 成本高:涉及线程挂起/唤醒、上下文切换
              • 高竞争下更稳定:避免大量线程空转自旋把 CPU 烧穿
    • wait/notify:线程协作的基础
      • wait/notify 使用规则
        • 必须在 synchronized 内调用,否则抛 IllegalMonitorStateException
        • wait() 会
          • 释放当前 monitor
          • 把线程放入该 monitor 的等待队列
          • 被唤醒后需要重新竞争锁
        • 永远用 while 检查条件,不用 if
          • 防止虚假唤醒
          • 防止被唤醒时条件已被其他线程改变
      • notify vs notifyAll
        • notify(): 随机唤醒一个等待线程
        • notifyAll(): 唤醒全部等待线程,让它们竞争并再次检查条件
        • 多数条件 prefer notifyAll,除非非常确定只存在一种等待条件且不会误唤醒
      • wait/notify vs sleep
        • sleep():不释放锁,属于 Thread 方法,主要用于延时
        • wait():释放锁,属于 Object 方法;用于条件等待
    • Condition 与 LockSupport:更现代的协作手段
      • Condition 相对 wait/notify 的优势
        • 可拥有多个条件队列
        • API 更清晰 await/signal/singalAll
        • 与 ReentrantLock 组合可实现更复杂的并发控制
      • LockSupport
        • park() 阻塞当前线程
        • unpark(thread):给某个线程发许可,让它从 park 返回
        • unpark() 可以先于 park 发生,比 wait/notify 更灵活
    • 可中断的生产者-消费者
    • Condition 版本

JUC 核心与 AQS

  • JUC 核心与 AQS
    • CAS 与原子类
      • CAS 是什么
        • CAS 本质是一个 CPU 原子指令
        • 比较内存位置的值是否等于 expected
        • 是则写入 update
        • 返回成功/失败
      • CAS 解决了什么
        • 解决复合更新在并发下的原子性
        • 避免锁的阻塞/唤醒,吞吐通常更高
      • CAS 的代价
        • 自旋重试:竞争大时空转,CPU 飙高
        • 可能导致某些线程长期失败
      • AtomicInteger/AtomicLong 的工作方式
        • AtomicInteger.incrementAndGet(),本质是
          • 循环:读旧值 -> 计算新值 -> CAS 尝试更新 -> 失败重试
        • 原子类=CAS 自旋 + volatile 语义,失败重试直到成功
      • ABA 问题
        • 问题描述
          • 线程 T1 看到值为 A,准备 CAS 成 B;期间 T2 把 A 改成 C 又改为 A。
          • T1 CAS 会成功,它不知道中间发生过变化
        • ABA 什么时候有害
          • 关心的是值有没与变过,而不是当前值是不是 A
        • 解决方案
          • 版本号:AtomicStampedReference(值+stamp)
          • 也有 AtomicMarkableReference(值+boolean 标记)
        • LongAddr 为什么比 AtomicLong 快
          • AtomicLong:所有线程竞争同一个 value,CAS 冲突高
          • LongAdder:把热点拆分成多个 Cell,线程尽量更新自己的 Cell,最后 sum 时聚合
    • AQS:JUC 的核心骨架
      • AQS 最重要的三件东西
        • state (volatile int):同步状态
        • CLH 变体 FIFO 队列:等待线程排队
        • acquire/release 模板方法:统一的获取/释放流程
        • AQS 用 state 表示资源,用队列管理竞争失败的线程,用 acquire/release 模板屏蔽排队、阻塞与唤醒细节,同步器只要实现 tryAquire/tryRelease
      • AQS 的两种模式
        • 独占:一次只允许一个线程获取
          • ReentrantLock、ReentrantReadWriteLock 的写锁
        • 共享:运行多个线程同时获取
          • Semaphore、CountDownLatch、读锁
      • acquire/release
        • acquire
          • tryAcquire() 尝试直接获取资源
          • 失败:把当前线程包装成 Node 入队
          • 自旋检查是否轮到自己(前驱是 head 且再 tryAquire)
          • 若仍然失败,park 阻塞
          • 被唤醒后继续循环
          • 要点
            • 不是一直自旋:会 park
            • 唤醒不是广播,通常唤醒一个合适的后继节点,避免惊群
        • release
          • tryRelease() 修改 state(释放资源)
          • 如果释放后资源可用,唤醒队列中的后继节点
      • 为什么 AQS 用队列
        • 避免一堆线程同时自旋抢锁造成 CPU 爆炸
        • FIFO 队列提供基本公平性基础
        • park/unpark 做阻塞唤醒,减少上下文切换次数(相对 monitor 更加可控)
    • ReentrantLock
      • ReentrantLock 相比 synchronized 的能力点
        • 可中断:lockInterruptibly()
        • 可超时:tryLock(timeout)
        • 可选择公平/非公平
        • 多 Condition(多个等待队列)
        • 缺点:必须手动释放锁,代码更啰嗦
      • 公平 vs 非公平
        • 公平锁:大致按照队列先来先服务,吞吐更低
        • 非公平锁:允许插队,先 CAS 抢一把,吞吐更高,可能造成某些线程等待更久
      • 可中断锁为什么重要
        • 线程在等锁时被取消/超时,希望立即退出而不是继续卡死
        • lock():等待锁期间不响应 interrupt
        • lockInterruptibly():能响应 interrupt,抛 interruptedException
    • Condition:两个队列(同步队列+条件队列)
      • Condition 的语义
        • await():释放锁+当前线程进入条件队列+park
        • signal():把条件队列头部线程转移到同步队列
        • 线程真正执行
          • 被 signal 转移到同步队列
          • 重新竞争锁成功 (acquire)
          • await 返回继续跑
      • 常见同步工具类
        • CountDownLatch(一次性门闩)
          • state = count
          • await():共享获取,直到 count == 0 全部放行
          • countDown():释放,count–,到 0 唤醒所有等待者
          • 适用场景
            • 主线程等待多个子任务完成
            • 服务启动前等待依赖就绪
          • count 到 0 后不能重置
        • Semaphore(信号量:限流/资源池)
          • state = permits
          • acquire():permits–,不够则排队阻塞
          • release():permits++,唤醒等待者
          • 适用场景
            • 限制并发数
            • 连接池/令牌桶
        • CyclicBarrier(客服用栅栏,非 AQS 典型)
          • 让一组线程在栅栏处互相等待,达到数量后一起继续
          • 可复用
          • 用于并行计算分段后汇总

线程池与并发工程实战

  • 线程池与并发工程实战
    • 不用线程池的问题
      • 线程创建/销毁成本高(栈、TLAB、调度)
      • 不可控:并发暴涨会把机器压垮
      • 缺乏治理能力:无法统一命名,监控、拒绝、限流、隔离
    • ThreadPoolExecutor
      • 七大参数
        • corePoolSize(核心线程数)
          • 默认不会受
          • 任务来时优先创建核心线程直到达到 core
        • maximumPoolSize(最大线程数)
          • 队列慢后,才会创建非核心线程,直到 max
          • 超过 max 再来任务 -> 触发拒绝策略
        • keepAliveTime(空闲存活时间)
          • 非核心线程空闲超时会回收
          • 若允许核心线程超时,则核心线程也会回收
        • workQueue (任务队列)
          • 决定任务是排队还是扩线程的关键
        • threadFactory(线程工厂)
          • 线程命名,daemon、优先级、UncaughtExceptionHandler
          • 工程强制:必须设置可读名称
        • handler(拒绝策略)
          • 线程数到 max 且队列满->触发拒绝
        • unit(时间单位)
      • 线程池执行流程
        • 若 workerCount < corePoolSize
          • 创建核心线程执行 task
        • 否则尝试入队 workQueue.offer(task)
          • 入队成功:等待线程从队列取任务
        • 入队失败,且 workerCount < maximumPoolSize
          • 创建非核心线程执行 task
        • 否则,拒绝策略 handler.rejectExecution(task, executor)
      • workQueue 选型
        • LinkedBlockingQueue(无界/大容量队列)
          • 特点
            • 极容易入队成功,导致线程数通常停在 core,不会扩到 max
            • 如果队列无界:任务堆积 -> 内存膨胀/延迟飙升,最终 OOM 或雪崩
          • 适用:任务流量可控,任务执行稳定,且能接受排队延迟
        • ArrayBlockingQueue(有界队列)
          • 特点
            • 队列有界,能把系统压力显式化
            • 延迟更可控,便于做背压与降级
          • 线上服务强烈推荐:有界队列+明确拒绝策略
        • SynchronousQueue(不存任务,直接提交)
          • 特点
            • 没容量,offer 必须被 worker 立即 take 才算成功
            • 适合快速扩线程,直到 max,然后拒绝
            • newCachedThreadPool 就是它,风险是线程数可能飙升
    • 拒绝策略
      • 四种内置策略
        • AbortPolicy(默认):抛 RejectedExecutionException
          • 适合你希望调用方显式失败,触发降级/重试逻辑
        • CallerRunsPolicy:调用方自己执行任务
          • 适合给上游施加背压(请求线程被拖慢,QPS 自然下降)
        • DiscardPolicy:静默丢弃
          • 适合:允许丢数据的场景(采样、非关键日志)
        • DiscardOldestPolicy:丢弃队列最老任务,再尝试入队
          • 适合:追求新任务更重要的场景
      • 工程化拒绝策略(建议自定义 handler)
        • 记录指标(reject count、queue size、active count)
        • 打点日志
        • 触发降级
        • 投递到补偿通道
    • Executors 工厂的问题
      • newFixedThreadPool
        • 默认用无界 LinkedBlockingQueue:堆积导致 OOM/延迟失控
      • newCachedThreadPool
        • 默认 SynchronousQueue + max=IntegerMAX_VALUE:线程数可以无限膨胀
      • newSingleThreadExecutor
        • 无界队列,同样堆积危险
      • 生产环境建议显式 new ThreadPoolExecutor:有界队列、合理 max、明确拒绝策略、线程命名与监控
    • 线程数怎么配置
      • CPU 密集型
        • 线程数 = CPU 核心数
        • 目标:减少上下文切换
      • IO 密集型
        • 线程数 = 核心数 * (1+等待时间/计算时间)
        • 工程上推荐压测(不同线程数)-> 看吞吐/延迟/CPU/上下文切换->找拐点
    • CompletableFuture
      • 解决的问题
        • 传统 Future 的痛点
          • 只能 get() 阻塞拿结果,组合多个异步任务很痛苦
          • 异常处理分散,超时与取消不优雅
          • 任务依赖关系需要手写线程/回调
        • CompletableFuture 的定位
          • 提供声明式的异步流水线
          • 支持任务依赖、并行、聚合、异常处理、完成回调
      • 核心心智模型
        • Stage(阶段)
          • 每个 thenXX 都是在构建一个新阶段
          • 上一个阶段完成后触发下一个阶段
          • 每个阶段都可能
            • 产出一个值
            • 抛异常
            • 被取消
        • Completion(完成态)
          • Completable 可能以三种方式结束
            • 正常完成(有结果)
            • 异常完成(exceptionally completed)
            • 被取消(cancelled)
        • Executor
          • 必须管理在哪个线程执行阶段
      • 线程池与执行线程
        • 默认线程池
          • supplyAsync/runAsync:不传 executor 时,默认走 ForkJoinPool.commonPool
          • commonPool 是全局共享池:你的业务、第三方库、框架都可能在用
          • 工程风险
            • 隔离性差
            • 阻塞任务不友好
            • 排障难
        • thenApply vs thenApplyAsync
          • thenApply(fn):默认在完成上游阶段的线程里执行
          • thenApplyAsync(fn):异步执行
            • 不传 executor:用 commonPool
            • 传 executor:用指定的线程池
      • 常用 API 分组
        • 创建与启动
          • completableFuture(value):已完成(同步值包装)
          • runAsync(Runnable, executor?):无返回值任务
          • supplyAsync(Supplier, executor?):有返回值任务
          • new CompletableFuture<>() + complete/completeExceptionally:手动控制完成
        • 转换 map 与消费 consume
          • thenApply(fn): T->U
          • thenAccept(consume): 消费结果(无返回)
          • thenRun(runnable):不关心结果,只做后续动作
        • 串联
          • thenCompose(fn): T->CompletableFuture,扁平化
            • 用于上一个异步结果决定下一个异步请求
        • 并行组合
          • thenCombine(other, (a,b)->c):两个都完成后合并
          • allOf(f1,f2,…):全部完成
          • anyOf(f1,f2,…):任意一个完成
          • applyToEither(other, fn):谁先完成用谁的结果
          • acceptEither:谁先完成就消费
        • 异常处理
          • exceptionally(ex -> fallback):异常时给兜底值
          • handle((res, ex) -> …):无论成功失败都处理
          • whenComplete((res, ex) -> …):做副作用,不改变结果
        • 最佳实践
          • 主链用 handler 或 exceptionally 做兜底
          • whenComplete 做日志与 metrics

并发容器、阻塞队列、并发设计

  • 并发容器、阻塞队列、并发设计
    • 并发容器解决的问题
      • 线程安全访问:多线程读写不会破坏结构、不丢数据
      • 性能:比 Collections.synchronizedXxx/Hashtable 更高吞吐
      • 语义更强:有些容器提供阻塞、延时、优先级等能力
    • 选型
      • Map -> ConcurrentHashMap
      • List -> 读多血少用 CopyOnWriteArrayList
      • Queue -> 无界非阻塞 ConcurrentLinkedQueue,有界阻塞用 ArrayBlockingQueue/LinkedBlockingQueue
      • Set -> ConcurrentHashMap.newKeySet()/CopyOnWriteArraySet
      • Deque -> ConcurrentLinkedDeque/LinkedBlockingDeque
    • ConcurrentHashMap
      • CHM 为什么比 Hashtable 快
        • Hashtable:方法级 sychornized,所有操作串行
        • CHM:更细粒度控制
          • 大部分读操作无锁
          • 写操作只锁定桶/节点,并结合 CAS
      • CHM 的核心结构
        • 底层是 Node<K,V>[] table(桶数组)
        • 桶内结构:链表(node)或红黑树
        • 当链表过长且 table 足够大时,会树化降级查找复杂度
      • put 的并发控制逻辑
        • table 未初始化:先初始化
        • 根据 hash 找桶下标 i
        • 如果桶为空:尝试 CAS 放入新 Node
        • 若桶非空
          • 若桶是迁移中:帮助扩容
          • 否则对桶头做 synchronzied 局部锁,在桶内插入/更新
        • 插入后可能触发 size 技术更新与扩容检查
    • CopyOnWriteArrayList(COW):读多写少
      • 核心机制
        • 写操作会
          • 加锁
          • 复制底层数组
          • 在新数组上修改
          • 用引用替换发布新数组
        • 读操作直接读数组快照,无损
      • 适用与不适用
        • 适用
          • 读远多于写
        • 不适用
          • 写频繁
          • 数组很大
    • 并发队列:非阻塞 vs 阻塞
      • ConcurrentLinkedQueue(CLQ):非阻塞队列
        • 基于 CAS 的链表队列
        • 适合:高并发无界、低延迟、允许忙等/自旋语义的场景
        • 特点
          • offer/poll 非阻塞
          • size() 可能是线性复杂度
      • BlockingQueue:核心语义
        • BlockingQueue 的关键是两种阻塞语义
          • 当队列为空:take() 阻塞直到有元素
          • 当队列满:put() 阻塞直到有空间
          • offer/poll 返回特殊值
    • 三大常用阻塞队列
      • ArrayBlockingQueue(ABQ)-有界数组队列
        • 底层数组环形缓冲区
        • 一般用一把锁 + 两个 condition
        • 特点
          • 有界:最重要的工程优势
          • 内存局部性好
          • 吞吐稳定,适合线程池工作队列
        • 生成服务最常用的稳健默认
      • LinkedBlockingQueue(LBQ)-链表队列(可无界/大容量)
        • 链表节点存储,内存分配频繁(节点对象)
        • 常见实现会用两把锁来提高并行度
        • 风险:堆积导致 OOM 与延迟失控
      • SynchronousQueue(SQ)-不存储元素的移交队列
        • 容量为 0,put 必须等待 take 直接接手
        • 特点:几乎不用排队,延迟低
    • DelayQueue/PriorityBlockingQueue:定时与优先级
      • DelayQueue 延时队列
        • 元素实现 Delayed,按到期时间排序
        • take() 会阻塞直到最近到期元素到期
        • 适用:超时重试、订单超时关闭、缓存过期处理(小规模)
        • 工程上:大量定时任务用时间论/调度框架
      • PriorityBlockingQueue(优先级队列)
        • 无界优先级堆
        • 高优先级先出队