Java 并发核心:synchronizedLock、volatile、线程池超全详解
2026/6/6 9:49:01 网站建设 项目流程

一、synchronized 与 Lock 区别 + synchronized 锁升级原理

(一)synchronized 和 Lock 五大核心区别

表格

对比项synchronizedLock(java.util.concurrent.locks)
底层实现JVM 层面,内置关键字,编译生成monitorenter/monitorexit字节码,自动释放锁API 层面,代码对象,依赖 AQS (AbstractQueuedSynchronizer),手动加解锁
锁获取释放自动锁:代码块 / 方法执行完毕、异常抛出自动释放锁,不会死锁漏释放手动锁:lock()加锁、unlock()解锁,必须在 finally 释放,忘记 unlock 会死锁
中断等待不可中断:线程阻塞等待锁时,无法被 interrupt 中断可中断:lockInterruptibly(),阻塞等待期间可接收中断异常
尝试非阻塞获取无,抢不到锁只能阻塞tryLock():无参立刻返回 true/false;带时间参数限时尝试获取锁
锁类型默认非公平锁,不可自定义默认 ReentrantLock 非公平,可构造传入 true 创建公平锁

补充细节:

  1. synchronized 可修饰实例方法、静态方法、代码块;Lock 只能作用于代码块。
  2. ReentrantLock 是可重入锁,synchronized 本身也是可重入锁(依赖对象 monitor 计数器)。

(二)synchronized 锁升级原理(JDK1.6 优化,锁从无→偏向→轻量级→重量级逐步升级,只能升级不能降级

1. 无锁状态

对象刚创建,未被任何线程抢占,对象头 Mark Word 无标记,无锁标记位01

2. 偏向锁(单线程频繁获取锁场景)
  1. 触发:第一个线程第一次获取锁,JVM 把线程 ID 存入对象 Mark Word,标记位01(偏向)
  2. 原理:后续同一线程再次获取锁,只需对比线程 ID,CAS 一次验证,无同步开销。
  3. 撤销:出现第二个线程竞争锁,偏向锁撤销,升级为轻量级锁;JVM 默认延时启用偏向锁(启动几秒后开启)。
3. 轻量级锁(多线程交替抢锁,无同一时刻竞争)
  1. 抢锁:线程在栈帧创建锁记录 Lock Record,CAS 把对象 Mark Word 指针指向栈锁记录。
  2. 自旋:抢锁失败自旋循环重试(空循环),不阻塞内核,用户态切换,避免操作系统线程挂起开销。
  3. 升级:自旋次数过多、多个线程同时竞争,自旋消耗 CPU,升级重量级锁,标记10
4. 重量级锁(依赖 OS 内核 mutex 互斥锁)
  1. 底层依赖操作系统内核,抢不到锁的线程直接阻塞挂起,放入监视器等待队列,内核态切换开销大。
  2. 对应传统 monitor 锁,synchronized 早期默认重量级锁,JDK1.6 优化引入锁分级。

小结:无锁→偏向锁 (单线程)→轻量级 (交替竞争自旋)→重量级 (激烈竞争阻塞)。

二、volatile 关键字:可见性、禁止指令重排原理

1. 三大特性:保证可见性、禁止指令重排序、不保证原子性(无法替代锁)

(1)可见性原理

CPU 缓存架构:每个 CPU 有高速缓存,变量先加载到 CPU 缓存再运算,多线程下 A 线程修改缓存值无法立刻刷新主存,B 线程读取主存旧数据 = 不可见。

  • volatile 修饰变量:
    1. 线程修改变量后,立刻强制刷新修改值到主内存
    2. 其他 CPU 缓存行失效,下次读取放弃缓存,直接从主存拉取最新数据,实现多线程可见。

经典案例:while 循环标记 flag,volatile 修饰 flag 可结束死循环,不加会死循环。

(2)禁止指令重排序原理

CPU 与编译器为提升效率,会在不改变代码结果前提下打乱代码执行顺序(指令重排)。volatile 依靠 ** 内存屏障(Memory Barrier)** 禁止重排:

  1. LoadLoad 屏障:读屏障,前面读指令先执行,再执行后续读;
  2. StoreStore 屏障:写屏障,前面写指令先落主存,再执行后续写;
  • 变量写操作前后插入 Store 屏障:写完立刻刷主存,写不会跑到后面代码;
  • 变量读前后插入 Load 屏障:读之前保证前面读写全部完成。

DCL 双重检查锁单例,private static volatile Singleton instance;就是靠 volatile 防止实例化指令重排导致空指针。

补充短板:不保证原子性

num++(读 - 改 - 写三步),volatile 无法保证原子,多线程自增结果错乱,需要 synchronized/Lock 保证原子。

三、ThreadPoolExecutor 线程池七大参数 + 四种拒绝策略(面试重中之重)

1. 七大构造参数详解

java

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
  1. corePoolSize:核心线程数线程池常驻存活线程,默认创建后空闲也不会回收;allowCoreThreadTimeOut=true时核心线程超时可回收。
  2. maximumPoolSize:最大线程数最大容纳线程 = 核心线程 + 非核心线程,maximumPoolSize-corePoolSize=非核心线程数量
  3. keepAliveTime:非核心线程空闲存活时间非核心线程闲置超过该时间,自动销毁,节省资源。
  4. unit:时间单位TimeUnit.SECONDS、MILLISECONDS(秒、毫秒等)。
  5. workQueue:阻塞队列,存放等待任务常用:
    • ArrayBlockingQueue:有界数组阻塞队列;
    • LinkedBlockingQueue:无界链表队列(容易 OOM);
    • SynchronousQueue:不存储任务,插入立刻创建新线程。
  6. threadFactory:线程工厂创建线程,可自定义线程名称、守护线程,方便日志排查。
  7. handler:任务满时拒绝策略,线程池满载(核心 + 非核心全工作、队列塞满)触发。

线程池任务执行流程

  1. 提交任务 → 当前运行线程数 < corePoolSize →新建核心线程执行任务
  2. 核心线程已满 → 任务放入阻塞队列 workQueue 排队;
  3. 队列存满 → 当前线程 < maximumPoolSize → 创建非核心线程执行任务
  4. 总线程达到 maxPoolSize + 队列满 → 触发拒绝策略。

2. 四种内置拒绝策略(RejectedExecutionHandler)

  1. AbortPolicy(默认策略)直接抛出RejectedExecutionException运行时异常,中断提交任务,系统感知报错。
  2. DiscardPolicy静默丢弃无法存入的任务,不抛异常、无日志,容易丢失数据,极少使用。
  3. DiscardOldestPolicy丢弃阻塞队列队头最早排队任务,尝试再次提交当前新任务。
  4. CallerRunsPolicy不新开线程、不抛异常,让提交任务的主线程执行任务,减缓任务提交速度,天然限流。

拓展:自定义拒绝策略

实现 RejectedExecutionHandler 接口,重写 rejectedExecution,自定义落库、消息重试、告警等业务逻辑。

四、总结

  1. synchronized 适合简单同步场景,开发简洁;高并发灵活控制场景选用 ReentrantLock;
  2. volatile 轻量级同步,解决可见性与重排,不能替代锁保证原子;
  3. 线程池复用线程,避免频繁创建销毁线程损耗,根据业务任务量合理配置核心线程与队列,按需选用拒绝策略。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询