3种常用的缓存读写策略详解
缓存与 DB 谁为准、何时写缓存,决定了一致性与复杂度。三种经典模式各有适用边界。 Cache Aside(旁路缓存,最常用)读:先读缓存,miss 则读 DB 并回填。写:先更新 DB,再删缓存(或延迟双删)。实现简单,但需防击穿与短暂不一致。 Read/Write Through读写都经缓存层代理,缓存负责同步 DB。应用代码干净,但缓存组件要承担写穿逻辑,多用于集成缓存产品。 Write Behind(写回)写先落缓存,异步批量刷 DB。写性能高,但丢电/宕机可能丢未刷盘数据,适合可容忍丢失的计数场景。 12Cache Aside 写路径:update DB → delete cache读路径:cache hit return / miss → load DB → set cache 实践建议高一致场景写后删缓存+短 TTL;热点 key 用互斥锁或逻辑过期防击穿。 延迟双删时间窗第二次删除延迟通常取主从同步时间 + 几百毫秒。可用 canal 订阅 binlog 精确删缓存。 多级缓存本地 Caffeine + Redis 注意广播失效或 TTL...
Redis 专题:缓存、数据结构、持久化、集群、阻塞与工程实践
Redis 既是缓存也是多功能数据结构服务器。学习路径建议从单线程模型与数据结构选型开始,再到持久化与高可用。 学习模块 缓存问题:穿透、击穿、雪崩、一致性。 数据结构:String/Hash/List/Set/ZSet + Bitmap/HyperLogLog/GEO/Stream。 持久化:RDB 快照与 AOF 日志及混合模式。 高可用:主从、Sentinel、Cluster 分片。 工程:阻塞排查、内存碎片、延迟任务与 Stream MQ。 为什么快内存操作、IO 多路复用单线程避免锁、高效数据结构(跳表、压缩列表/intset 等演进)。 实践建议键名规范 biz:entity:id;设置 maxmemory 与淘汰策略;慢日志与 latency doctor 常态化。 版本与模块Redis 7/8 新特性关注 function、ACL 细粒度、多 part AOF。TLS、ACL 默认账号禁用是安全基线。 与 Memcached 对比Redis 富数据结构、持久化、复制;M...
虚拟线程常见问题总结
平台线程 vs 虚拟线程平台线程 1:1 映射 OS 线程,创建成本高;虚拟线程由 JVM 调度,阻塞 IO 时挂起让出载体线程(平台线程),适合高并发阻塞型任务。 创建方式12Thread.startVirtualThread(() -> fetchRemote());Executors.newVirtualThreadPerTaskExecutor(); 不适合的场景CPU 密集计算不会变快;在 synchronized 或 native 方法内阻塞可能导致 pinning,载体线程无法释放。优先用 ReentrantLock,或减少临界区。 观测jcmd Thread.dump_to_file 可区分 virtual/carrier,排查 pinned 事件。 课堂外的思考学习「虚拟线程常见问题总结」时,我把自己放在线上值班的情境里:如果告警与这一主题相关,我能否在较短时间内建立假设并用数据验证?这种提问方式逼迫我从「看过」变成「讲得清楚、做得到」,也避免笔记沦为标题摘抄。 与同事的讨论方式我会用白板画出状态机、内存布局或调用链,请同事挑错。若被追问「...
Java并发常见面试题总结(下)
死锁四要素互斥、占有且等待、不可抢占、循环等待。排查用 jstack 看 Found one Java-level deadlock,预防则固定加锁顺序或使用 tryLock 超时。 ThreadLocal 泄漏线程池场景下线程复用,ThreadLocalMap 的 Entry 弱引用 key 可能被回收,但 value 仍被强引用直到 remove。务必 finally 中 remove。 CountDownLatch vs CyclicBarrier前者一次性倒数,典型主线程等多 worker;后者可重用,适合分阶段并行计算。Semaphore 控制并发度,不是「栅栏」。 综合设计题设计一个限流 + 异步落库组件:Semaphore 控制并发写,BlockingQueue 缓冲,线程池消费,暴露 metrics 观察拒绝与堆积。 课堂外的思考学习「Java并发常见面试题总结(下)」时,我把自己放在线上值班的情境里:如果告警与这一主题相关,我能否在较短时间内建立假设并用数据验证?这种提问方式逼迫我从「看过」变成「讲得清楚、做得到」,也避免笔记沦为标题摘抄。 与同事的讨论方式...
Java并发常见面试题总结(中)
volatile 到底保证什么保证可见性与禁止特定重排序,不保证复合操作原子性。典型用途:状态标志、双重检查单例的引用发布。 happens-before 速记程序次序规则、监视器锁规则、volatile 规则、线程 start/join 规则——面试时用一条链路串起来,比罗列名词更有说服力。 线程池五参数core、max、keepAlive、workQueue、handler。拒绝策略不是「越大越好」:有界队列 + CallerRuns 往往比无界队列更能暴露背压。 模拟题线程池队列长度 100,core=4,max=8,来了 200 个短任务,画出任务在 core 线程、队列、max 扩容与拒绝策略之间的流转。 课堂外的思考学习「Java并发常见面试题总结(中)」时,我把自己放在线上值班的情境里:如果告警与这一主题相关,我能否在较短时间内建立假设并用数据验证?这种提问方式逼迫我从「看过」变成「讲得清楚、做得到」,也避免笔记沦为标题摘抄。 与同事的讨论方式我会用白板画出状态机、内存布局或调用链,请同事挑错。若被追问「更高并发或更老 JDK 版本...
ThreadLocal 详解
实现要点每个 Thread 持有 ThreadLocalMap,key 是 ThreadLocal 弱引用,value 强引用。get/set 实际在当前线程 map 里读写,实现线程封闭。 典型用途SimpleDateFormat 非线程安全,用 ThreadLocal 包装;MDC 日志 traceId;Sa-Token 等上下文传递。 泄漏场景线程池 worker 长期存活,若未 remove,大对象 value 无法回收。规范:try/finally 中 threadLocal.remove()。 继承性InheritableThreadLocal 在创建子线程时复制父值;线程池复用 worker 时不会自动传递,需要 TransmittableThreadLocal 等增强方案。 课堂外的思考学习「ThreadLocal 详解」时,我把自己放在线上值班的情境里:如果告警与这一主题相关,我能否在较短时间内建立假设并用数据验证?这种提问方式逼迫我从「看过」变成「讲得清楚、做得到」,也避免笔记沦为标题摘抄。 与同事的讨论方式我会用白板画出状态机、内存布...
乐观锁和悲观锁详解
概念对照悲观锁假设冲突常发生,先加锁再操作(synchronized、SELECT FOR UPDATE)。乐观锁假设冲突少,提交时检测版本(CAS、UPDATE … WHERE version=?)。 Java 侧例子AtomicInteger 是典型乐观;ReentrantLock 是悲观。库存扣减:低竞争用 CAS 循环,高竞争乐观自旋浪费 CPU,应改悲观或分段锁。 数据库版本号模式12UPDATE account SET balance = balance - 100, version = version + 1WHERE id = 1 AND version = 5; 影响行数为 0 表示冲突,业务层重试或返回失败。与 Java CAS 的「期望值不匹配则失败」同构。 课堂外的思考学习「乐观锁和悲观锁详解」时,我把自己放在线上值班的情境里:如果告警与这一主题相关,我能否在较短时间内建立假设并用数据验证?这种提问方式逼迫我从「看过」变成「讲得清楚、做得到」,也避免笔记沦为标题摘抄。 与同事的讨论方式我会用白板画出状态机、内存布局或调用链,请同事挑错。若被...
JMM(Java 内存模型)详解
为什么需要 JMM编译器与 CPU 会重排序,每个线程又有本地工作内存缓存。JMM 规定哪些写对其它线程可见,从而在不牺牲性能的前提下给出可推理的并发语义。 happens-before 实例对同一把锁 unlock happens-before 后续 lock;volatile 写 happens-before 后续读;线程 start 前动作 happens-before 线程内任意动作。 与 JVM 内存区域区分JMM 是语义模型,讨论可见性/有序性;堆、栈是运行时布局。面试别混:「栈存局部变量」不等于「工作内存就是栈」。 小实验两线程各写普通变量与 volatile 变量交叉读,用 JMH 或简单循环观察无同步时的非预期结果,加深「看不到最新值」的直觉。 课堂外的思考学习「JMM(Java 内存模型)详解」时,我把自己放在线上值班的情境里:如果告警与这一主题相关,我能否在较短时间内建立假设并用数据验证?这种提问方式逼迫我从「看过」变成「讲得清楚、做得到」,也避免笔记沦为标题摘抄。 与同事的讨论方式我会用白板画出状态机、内存布局或调用链,请同事挑错。若被追问「...
Java 线程池详解
提交流程任务先来占 core 线程;core 满则入队;队满则扩到 max;仍满则触发拒绝策略。keepAlive 回收超出 core 的空闲线程。 队列选择ArrayBlockingQueue 有界、数组实现;LinkedBlockingQueue 可选无界;SynchronousQueue 直接交接,适合 Cached 风格;PriorityBlockingQueue 按优先级。 示例12345ThreadPoolExecutor pool = new ThreadPoolExecutor( 4, 8, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(200), r -> { Thread t = new Thread(r); t.setName("biz-" + t.getId()); return t; }, new ThreadPoolExecutor.CallerRunsPolicy()); CallerRuns 让提交线程执行溢出任务...
Java 线程池最佳实践
不要用 Executors 的原因newFixedThreadPool 使用无界 LinkedBlockingQueue,任务堆积时内存风险高;newCachedThreadPool 最大线程 Integer.MAX_VALUE,可能打爆 CPU。 推荐创建方式ThreadPoolExecutor 显式参数 + 有界队列 + 自定义 ThreadFactory(命名 prefix 方便 jstack)+ RejectedExecutionHandler 记录指标。 隔离与关闭不同业务用独立线程池,避免慢任务拖垮快任务。shutdown → awaitTermination → shutdownNow 是优雅关闭模板,配合 Spring 的 @PreDestroy 或 JVM hook。 监控项活跃线程数、队列深度、拒绝次数、任务耗时 P99。队列持续满且 active≈max,说明下游瓶颈而非简单「加线程」。 课堂外的思考学习「Java 线程池最佳实践」时,我把自己放在线上值班的情境里:如果告警与这一主题相关,我能否在较短时间内建立假设并用数据验证?这种提问方式逼迫我从「看过...