synchronize 实现原理
同步代码块是使用 monitorenter 和 monitorexit 指令实现的,同步方法(在这看不出来需要看 JVM 底层实现)依靠的是方法修饰符上的 ACC_SYNCHRONIZED 实现。 这两条字节码指令背后是同一套机制:Monitor(管程/监视器锁)。本文从字节码、对象头、锁升级三个层次把 synchronized 拆透,并说清它为什么从”重量级”变成了今天的样子。 字节码层:两种同步形态同步代码块编译后在前后分别插入 monitorenter / monitorexit(异常路径还有一对隐式的 monitorexit,保证锁一定释放): javap -c SyncDemo.class# 8: monitorenter# 9: aload_0# 10: invokevirtual ...# 13: monitorexit 同步方法没有这两条指令,而是方法访问标志置为 ACC_SYNCHRONIZED:JVM 调用方法时检查该标志,先获取 monitor 再执行方法体。形态不同,本质相同——都是对某个对象的 monitor 的获取与释放:代码...
volatile 实现原理
在 JVM 底层 volatile 是采用“内存屏障”来实现的 缓存一致性协议(MESI协议)它确保每个缓存中使用的共享变量的副本是一致的。其核心思想如下:当某个 CPU 在写数据时,如果发现操作的变量是共享变量,则会通知其他 CPU 告知该变量的缓存行是无效的,因此其他 CPU 在读取该变量时,发现其无效会重新从主存中加载数据 两条原理点到了内存屏障与 MESI,但还差三层把原理闭环:volatile 到底保证了什么(语义)、JMM 如何用 happens-before 表述、以及它为什么不保证原子性。本文补齐。 语义层:两个保证与一个不保证 可见性:对 volatile 变量的写,完成后对其他线程立即可见;读总是读到最新写 有序性:禁止 volatile 读/写与前后普通内存操作重排序(全屏障效果) 不保证原子性:volatile count++ 的读-改-写三步之间仍可被切换,并发下丢更新——这是 volatile 与锁/原子类的根本分界 JMM 表述:happens-before 规则Java 内存模型用 happens-before 定义可...
说说线程安全问题
线程安全是多线程领域的问题,线程安全可以简单理解为一个方法或者一个实例可以在多线程环境中使用而不会出现问题。 在 Java 多线程编程当中,提供了多种实现 Java 线程安全的方式: 最简单的方式,使用 Synchronization 关键字 使用 java.util.concurrent.atomic 包中的原子类,例如 AtomicInteger 使用 java.util.concurrent.locks 包中的锁 使用线程安全的集合 ConcurrentHashMap 使用 volatile 关键字,保证变量可见性(直接从内存读,而不是从线程 cache 读) 工具清单齐了,但”什么问题需要这些工具”才是起点。线程安全问题的根源只有三个来源:原子性、可见性、有序性。本文按”三源定位 → 工具匹配 → 设计优先级”展开,把清单变成决策路径。 三大根源与典型症状 原子性:操作序列被线程切换打断。症状:计数器少算(i++ 丢失更新)、check-then-act 竞态(先判空再 put 出现重复)、懒加载双实例 可见性:一个线程的写对另一个线程不可见(各自 CPU 缓存副本)...
线程的生命周期
New:新建 Runnable:就绪 Running:运行 Blocked:阻塞 Dead:死亡 五状态模型是操作系统教材的视角;Java 层面真正用于排障的是 Thread.State 枚举的六状态。两套模型的映射关系、以及每个阻塞状态对应的排查手段,才是这个知识点的实战价值。 Java 的六状态与五状态映射 Thread.State 含义 对应五状态 NEW 已 new 未 start New RUNNABLE 可运行/运行中(含等 CPU 与等 IO 的”假运行”) Runnable + Running BLOCKED 等监视器锁(synchronized 竞争失败) Blocked WAITING 无限期等待他线程动作(wait/join/park) Blocked TIMED_WAITING 带超时的等待(sleep(t)/wait(t)/parkNanos) Blocked TERMINATED 执行结束或异常退出 Dead 关键差异:Java 把”就绪”与”运行”合并为 RUNN...
线程池的几种方式与使用场景
在 Executors 类里面提供了一些静态工厂,生成一些常用的线程池。 newFixedThreadPool:创建固定大小的线程池。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。 newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小。 newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。 newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。 newSingleThreadScheduledExecutor:...
讲讲线程池的实现原理
当提交一个新任务到线程池时,线程池的处理流程如下: 线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。 线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。 线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。 三步流程是 ThreadPoolExecutor.execute 的行为描述,但实现层面真正精妙的是:一个 int 变量(ctl)同时编码了线程池状态与线程数,以及”Worker 线程自取任务”的运行模型。本文把流程落到源码机制,并讲清拒绝策略与生命周期。 ctl:一个原子整数的双重编码ThreadPoolExecutor 用 AtomicInteger ctl 的高 3 位表示池状态(RUNNING / SHUTDOWN / STOP / TIDYING / TERMINATED),低 29 位表示线程数。一次 CAS 即可原子地同时...
ThreadLocal 原理分析
ThreadLocal 提供了线程本地变量,它可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同。ThreadLocal 相当于提供了一种线程隔离,将变量与线程相绑定。 “线程隔离”是效果,原理要回答四个问题:副本存在哪(数据结构)、为什么用弱引用(内存设计)、内存泄漏怎么发生(使用陷阱)、线程池场景为何尤其危险。本文逐层拆解。 存储结构:Map 挂在线程上关键设计:不是 ThreadLocal 持有 Map,而是每个 Thread 对象持有一个 ThreadLocalMap: Thread.threadLocals 字段即 ThreadLocalMap Map 的 key 是 ThreadLocal 实例(弱引用),value 是副本值(强引用) set/get 时以当前 ThreadLocal 为 key 操作当前线程自己的 Map,天然无锁、无竞争 public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap ma...
说说 Exchanger 原理
当一个线程到达 exchange 调用点时,如果它的伙伴线程此前已经调用了此方法,那么它的伙伴会被调度唤醒并与之进行对象交换,然后各自返回。如果它的伙伴还没到达交换点,那么当前线程将会被挂起,直至伙伴线程到达——完成交换正常返回;或者当前线程被中断——抛出中断异常;又或者是等候超时——抛出超时异常。 这段描述覆盖了 Exchanger 的行为面,但原理面还有三层值得展开:两两配对的槽位(slot)机制、为什么设计成多槽数组、以及它真正发光的生产者-消费者场景。 核心语义:两线程的会合点(rendezvous)Exchanger 是点对点双向数据交换:两个线程在 exchange 点会合,各自把对象交给对方后继续执行。与队列的”单向传递”不同,交换是原子互换——双方都拿到对方的数据。 Exchanger<List<Record>> ex = new Exchanger<>();// 生产线程:装满一批 → 交出空批换回List<Record> buf = new ArrayList<>(BATCH);while (runn...
说说 Semaphore 原理
Semaphore 直译为信号。实际上 Semaphore 可以看做是一个信号的集合。不同的线程能够从 Semaphore 中获取若干个信号量。当 Semaphore 对象持有的信号量不足时,尝试从 Semaphore 中获取信号的线程将会阻塞。直到其他线程将信号量释放以后,阻塞的线程会被唤醒,重新尝试获取信号量。 信号量模型(P/V 操作)由 Dijkstra 提出,是操作系统课的经典内容;Java 的 Semaphore 把它落成了 AQS 共享模式的一个应用。本文讲清三件事:AQS 层如何实现许可计数、公平与非公平的差异、以及它在限流场景的正确用法与常见误用。 模型:许可(permits)池Semaphore 维护一组许可: acquire():取一个许可,无许可则阻塞(P 操作) release():还一个许可,可能唤醒等待者(V 操作) 构造参数 permits 决定同时允许多少个线程通过:permits=1 时退化为互斥锁;permits=N 时允许 N 个并发 Semaphore sem = new Semaphore(3); ...
说说 CyclicBarrier 原理
CyclicBarrier 是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点(CommonBarrierPoint)。因为该 barrier 在释放等待线程后可以重用,所以称它为循环的 barrier。 定义一句话讲完,原理要回答三个问题:到齐之前线程停在哪(等待机制)、到齐那一刻发生了什么(放行与 barrierAction)、为什么能循环(generation 代际)。本文按这三问展开,并补上破损语义与实战场景。 等待机制:ReentrantLock + Condition与 CountDownLatch 基于 AQS 共享模式不同,CyclicBarrier 内部用一把 ReentrantLock 加一个 Condition(trip) 实现: 每个线程调用 await() 时先持锁,把计数器 count 减一 count 不为 0:当前线程在 trip 条件上 await() 释放锁并挂起,等待被唤醒 count 减到 0:**当前线程就是”最后一个到齐者”**,由它执行屏障动作并唤醒所有人 CyclicBarrier barrier = new Cyc...















