Java多线程并发高频面试100题(完整版·含答案·背诵版)

一、基础概念(1-20 题)

1.什么是进程?什么是线程?

1、进程(Process) :操作系统资源分配的最小单位 ,是一段程序的一次运行过程。进程拥有独立内存空间、堆、方法区、文件句柄,进程之间内存完全隔离,互不共享。操作系统调度成本极高,进程切换开销大。
2、线程(Thread) :操作系统CPU调度的最小单位 ,隶属于进程,一个进程至少包含一条主线程。线程共享所属进程的堆、方法区、常量池;仅私有程序计数器、虚拟机栈、本地方法栈。线程切换成本极低,轻量化。
通俗理解 :进程=独立工厂;线程=工厂里工人;工厂独立厂房材料互不通用,工人共享工厂资源,协同干活。
核心总结:进程隔离资源,线程共享资源;进程开销重,线程开销轻。

2.线程和进程区别?

进程独立内存,切换开销大;线程共享内存,切换轻量,通信简单。

3.并发与并行区别?

1、并发(Concurrency)同一时间段 内多个任务交替执行。单CPU核心通过时间片快速切换线程,宏观看上去同时运行,微观上同一时刻只有一个任务执行。线程之间频繁切换、交替执行。
2、并行(Parallelism)同一时刻 多个任务同时执行。必须依赖多核CPU,多个物理核心同时处理多条线程,真正意义上一起运行,无切换执行。
通俗比喻

并发:一个人吃两碗饭,来回交替扒饭;

并行:两个人同时各吃一碗饭。
核心总结(面试必背) :并发是交替执行、宏观同时 ;并行是同时执行、物理多核。Java多线程默认大多是并发,只有多核才能实现并行。

4.Java 程序默认有几个线程?

1、标准答案 :一个普通 Java 程序默认至少 2 个线程 ,分别是主线程、垃圾回收线程。
2、详细底层拆解(面试加分)

主线程(Main Thread) :JVM 自动创建,执行 main() 方法,处理业务代码、程序入口。

GC 垃圾回收线程 :守护线程,后台持续监控堆内存,自动回收无效对象,防止内存溢出。
3、拓展:真实运行默认线程数量

在 JDK8 环境下,简单空main程序实际默认启动 **5~7条线程**:主线程、GC线程、监控线程、信号分发线程、销毁钩子线程。
4、面试总结 :背诵极简答案:最少2条:主线程+GC线程;实际运行含后台监控线程共7条左右。

5.创建线程有几种方式?(六种+完整代码+面试必背)

面试标准答案 :Java 创建线程一共6种方式 ,分别为:继承Thread类、实现Runnable接口、Callable+FutureTask、匿名内部类、Lambda表达式、线程池。
1、继承 Thread 类(底层源码)

特点:单继承、无法复用、无返回值。

java 复制代码
class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("继承Thread方式创建线程");
    }
}
// 使用:new MyThread().start();

2、实现 Runnable 接口(最常用)

特点:避免单继承、代码解耦、无返回值。

java 复制代码
class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("实现Runnable接口创建线程");
    }
}
// 使用:new Thread(new MyRunnable()).start();

3、Callable + FutureTask(有返回值)

特点:可抛出异常、有返回结果、可获取执行结果。

java 复制代码
Callable<Integer> callable = () -> {
    System.out.println("Callable有返回值线程");
    return 1024;
};
FutureTask<Integer> task = new FutureTask<>(callable);
new Thread(task).start();

4、匿名内部类(简化写法)

特点:临时线程、无需单独定义类。

java 复制代码
new Thread(){
    @Override
    public void run() {
        System.out.println("匿名内部类线程");
    }
}.start();

5、Lambda 表达式(JDK8+最简)

特点:代码极简、开发最常用。

java 复制代码
new Thread(() -> System.out.println("Lambda简洁线程")).start();

6、线程池创建(生产唯一推荐)

特点:复用线程、管控并发、禁止频繁创建销毁。

java 复制代码
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.execute(() -> System.out.println("线程池执行任务"));

面试高频总结

① 无返回值优先 Runnable

② 需要返回值用 Callable

③ 线上业务全部使用线程池,禁止手动new Thread。

6.Runnable 和 Callable 区别?(超详细背诵版+源码对比)

1、核心定义

两者都是用于定义线程任务的函数式接口,用于封装线程执行逻辑。
2、五大核心区别(面试必背)

对比维度 Runnable Callable
返回值 无返回值,核心方法为 void run() 有泛型返回值,核心方法为 V call()
异常抛出 方法无 throws 声明, 异常只能在内部 try-catch 处理 方法声明 throws Exception, 异常可向上层抛出
JDK 版本 JDK1.0 原生古老接口 JDK1.5 新增接口
使用载体 可直接传入 Thread、线程池执行 必须包装为 FutureTask 后,才能放入 Thread 执行
应用场景 单纯执行异步任务,无需获取执行结果 需要获取线程执行返回结果的业务场景

3、底层源码极简展示

java 复制代码
// Runnable源码
@FunctionalInterface
public interface Runnable {
    void run();
}

// Callable源码
@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

4、补充面试高频考点

① Thread类只能接收Runnable,不能直接接收Callable;

② Callable必须依赖 FutureTask 适配器包装才能放入线程;

③ 线程池submit()方法支持Callable,execute()只支持Runnable;
5、一句话总结

不需要返回值用Runnable,需要返回值、抛异常用Callable。

7.start() 和 run() 区别?(满分详细版+底层原理+代码演示)

1、核心本质区别(面试必背)

start():真正开启线程 。属于Thread原生方法,调用后进入就绪状态 ,由JVM获取CPU时间片后自动回调run(),存在线程切换,异步执行。

run():普通成员方法 。重写业务逻辑,直接调用不会开启新线程,仅在当前线程同步执行,无多线程特性。
2、五大详细区别

对比维度 start() run()
线程状态 新建→就绪,开启新线程 无状态变化,当前线程执行
调用次数 只能调用一次,二次调用抛异常 可重复无限调用
执行方式 异步、多线程并发 同步、串行执行
底层原理 调用 native 本地方法,向操作系统申请线程 纯 Java 代码,普通方法压栈执行
异常抛出 线程内部异常,不影响主线程 同步抛出,直接阻塞当前主线程

3、代码演示直观区别

java 复制代码
Thread thread = new Thread(() -> System.out.println("执行线程任务"));
thread.run();  // 主线程执行,无新线程
thread.start();// 开启新子线程,异步执行
// thread.start(); 二次调用直接抛出:IllegalThreadStateException

4、高频面试坑点

① start()底层是native 方法,由操作系统创建内核线程;

② 一个线程对象只能start一次,重复调用抛出非法线程状态异常;

③ 业务绝对不要手动调用run(),会丧失多线程并发能力。
5、一句话总结start()申请资源开启新线程;run()只是一段普通业务代码。

8.线程有几种生命周期状态?(七种完整版+流转图+面试必考)

1、标准答案 :Java 线程生命周期一共 7 种状态,出自 Thread.State 枚举类,分别为:

新建、就绪、运行、阻塞、等待、超时等待、终止。

2、七种状态详细解释(面试必背)

NEW(新建) :创建线程对象,未调用start(),没有分配CPU资源。

RUNNABLE(就绪) :调用start(),准备就绪,等待CPU时间片,包含操作系统就绪+运行。

RUNNING(运行) :获取CPU时间片,正在执行run()业务逻辑。

BLOCKED(阻塞) :未抢到synchronized重量级锁,阻塞挂起,不释放CPU。

WAITING(无限等待) :无限等待别人唤醒,调用wait()、join(),必须手动唤醒 ,释放锁。

TIMED_WAITING(超时等待) :限时等待,sleep(time)、wait(time),时间到自动唤醒。

TERMINATED(终止) :线程代码执行完毕,线程彻底死亡,不可重启。
3、线程核心流转流程

新建 → start() → 就绪 → 抢占CPU → 运行 →(阻塞/等待/超时等待)→ 唤醒回到就绪 → 运行 → 执行完毕 → 终止。
4、高频面试坑点

① Java源码枚举没有运行态RUNNING ,底层把就绪+运行合并为RUNNABLE;

② BLOCKED只针对synchronized,Lock锁阻塞是WAITING;

③ 死亡线程不能再次start(),直接抛异常;

④ sleep进入超时等待、wait进入无限等待,都会释放CPU,sleep不释放锁、wait释放锁。
5、一句话总结七态核心:两等一阻、新建就绪、运行终止。

9.sleep() 和 wait() 区别?(满分完整版+表格对比+面试必考)

1、一句话极简背诵

sleep 属于线程、不释放锁、不必须同步;wait 属于对象、释放锁、必须在同步代码块。
2、六大核心区别(面试必背表格)

对比维度 sleep() wait()
归属类 Thread 静态方法 Object 成员方法
锁行为 不释放锁、不释放资源 主动释放锁
使用位置 任意位置使用,无需同步 必须在 synchronized 同步代码块 / 方法内
唤醒条件 时间到自动唤醒 必须被 notify ()/notifyAll () 手动唤醒
线程状态 进入 TIMED_WAITING 超时等待 进入 WAITING 无限等待
异常抛出 必须捕获 InterruptedException 必须捕获 InterruptedException

3、底层原理通俗解释

sleep() :单纯让线程休眠指定时间,CPU暂停执行,线程抱着锁睡觉,其他线程无法抢占锁;

wait() :主动放弃当前持有的锁,进入对象等待池,别人唤醒后再重新竞争锁。
4、代码直观演示锁区别

java 复制代码
// sleep不释放锁:别的线程进不来
synchronized (obj){
    Thread.sleep(2000); // 抱着锁休眠
}

// wait释放锁:别的线程可以抢占锁
synchronized (obj){
    obj.wait(); // 直接放开锁进入等待
}

5、高频面试坑点

① wait 必须搭配 synchronized,不加锁直接调用报:IllegalMonitorStateException;

② sleep 不会释放锁,生产高并发不要写长时间sleep;

③ wait 一般写在 while 循环防止虚假唤醒。
6、终极总结sleep 休眠不撒手、wait 等待放开手。

10.yield () 作用?

让出 CPU 执行权,回到就绪态,不释放锁。

11.join () 作用?

等待当前线程执行完毕,再继续执行主线程。

12.线程终止方式?(四种完整版+全部代码+生产规范)

面试标准答案

Java 线程终止一共 4种方式 :正常执行结束、volatile标记位终止、interrupt中断终止、stop暴力终止(废弃)。
1、方式一:正常执行结束(天然终止)

线程run()方法代码执行完毕,自动进入TERMINATED终止状态,无任何额外代码,最安全。
2、方式二:volatile 布尔标记位终止(生产最推荐)

原理:利用volatile可见性,外部修改标记位,线程主动判断退出,优雅安全、无锁、无异常。

java 复制代码
// volatile标记位终止线程
public class ThreadStopFlag {
    // volatile保证可见性
    private static volatile boolean flag = true;
    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (flag){
                // 业务逻辑
            }
            System.out.println("线程优雅退出");
        }).start();
        Thread.sleep(2000);
        // 修改标记位,终止线程
        flag = false;
    }
}

3、方式三:interrupt() 中断终止(官方推荐)

原理:修改线程中断标记,不会强制杀死线程,分为两种中断:

阻塞状态中断 :线程处于sleep/wait/join,直接抛出异常、清除中断标记;

运行状态中断 :仅打标记,线程自行判断isInterrupted()退出。

java 复制代码
// interrupt优雅终止代码
public class ThreadInterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()){
                // 执行业务
            }
            System.out.println("线程收到中断信号,主动退出");
        });
        t1.start();
        Thread.sleep(2000);
        // 发送中断信号
        t1.interrupt();
    }
}

4、方式四:stop() 暴力终止(官方废弃、禁止使用)

java 复制代码
// 禁止使用!已废弃
thread.stop();

5、四种方式优缺点总结(面试必背表格)

终止方式 优点 缺点
正常执行结束 安全、无副作用,线程天然终止 无法手动控制终止时机,仅能等待代码执行完毕
volatile 标记位 简单优雅、可见性强、无异常,代码侵入性低 无法终止阻塞中的线程,仅能控制运行态线程主动退出
interrupt 中断 官方推荐、可处理阻塞线程,适配全场景线程终止 需要手动判断中断标记,编码复杂度略高于标记位方案
stop 暴力终止 可立即杀死线程,强制终止速度快 会造成数据错乱、锁不释放、内存损坏,官方已废弃,生产禁止使用

6、高频面试坑点

① interrupt 只是打标记 ,不会直接杀死线程;

② 线程处于阻塞状态,调用interrupt会直接抛异常;

③ 生产环境禁止使用stop、destroy

④ 阻塞线程终止优先interrupt,运行中线程终止优先volatile标记位。
7、一句话总结运行线程用标记、阻塞线程用中断、禁止暴力stop。

13.interrupt () 中断原理?

修改中断标记,不会立刻终止,线程自行判断处理。

14.守护线程和用户线程区别?(满分完整版+通俗比喻+面试必考)

1、核心定义

用户线程(非守护线程) :普通业务线程,JVM 必须等待所有用户线程执行完毕,才会正常退出,属于前台线程

守护线程(后台线程) :专门为用户线程服务的后台线程,只要全部用户线程结束,无论守护线程是否执行完毕,JVM 强制杀死守护线程并退出。
2、五大核心区别(面试必背表格)

对比维度 用户线程(非守护线程) 守护线程(后台线程)
生命周期 独立运行,代码执行完毕才自动终止,生命周期自主可控 完全依附用户线程,所有用户线程结束后,守护线程会被 JVM 强制销毁,无自主生命周期
JVM 退出条件 只要存在任意一条用户线程,JVM 就不会正常退出 当 JVM 中仅剩余守护线程时,JVM 会直接强制退出,无需等待守护线程执行完毕
线程优先级 默认优先级为 5,优先级较高,可优先抢占 CPU 资源 优先级偏低,仅在用户线程空闲时获取 CPU 资源,后台静默运行
异常处理 线程抛出异常会独立向上抛出,不影响其他线程的正常执行 线程抛出异常一般会被 JVM 静默忽略,不会中断其他业务线程
典型应用场景 核心业务逻辑、接口请求处理、数据计算、订单交易等前台核心业务 GC 垃圾回收、JVM 监控线程、服务心跳检测、日志采集、定时巡检等后台辅助服务

3、通俗比喻

用户线程 = 顾客 ;守护线程 = 保洁服务员

顾客全部走光(用户线程结束),保洁立刻下班(守护线程强制销毁),门店关门(JVM退出)。
4、代码演示区别

java 复制代码
public class DaemonThreadDemo {
    public static void main(String[] args) {
        Thread daemonThread = new Thread(() -> {
            while (true){
                // 无限循环任务
            }
        });
        // 设置为守护线程,必须在start()之前
        daemonThread.setDaemon(true);
        daemonThread.start();
        // 主线程(用户线程)休眠2秒结束
        Thread.sleep(2000);
        System.out.println("主线程执行结束");
        // 主线程结束,JVM直接关闭,守护线程被强制杀死
    }
}

5、高频面试坑点

setDaemon(true) 必须在 start() 之前执行 ,否则报错;

② 守护线程不能用于写入IO、事务等关键业务,JVM会强制终止导致数据丢失;

③ Java默认全部线程都是用户线程,GC是典型守护线程;

④ 守护线程适合做:监控、日志、定时巡检、心跳保活。
6、一句话总结用户线程决定JVM生死,守护线程陪跑服务、随用户线程一起消亡。

15.如何设置守护线程?(完整版+代码+硬性规则)

1、核心方法 :使用线程自带方法 setDaemon(boolean on) ,传入 true 即为守护线程,false 为默认用户线程。
2、硬性语法规则(面试必考)

必须在 start() 之前设置 ,线程启动后无法修改守护状态;

② 不能把正在运行的线程改为守护线程,会抛出 IllegalThreadStateException;

③ 主线程默认是用户线程,不可修改为守护线程。
3、完整标准代码示例

java 复制代码
public class SetDaemonDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (true){
                System.out.println("守护线程后台执行...");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 必须在start()之前设置守护线程
        thread.setDaemon(true);
        thread.start();
        // 主线程休眠3秒后结束
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程结束,JVM退出,守护线程强制终止");
    }
}

4、生产开发规范

① 守护线程不建议执行业务事务、文件写入、数据落库;

② 守护线程适合:日志采集、心跳检测、监控、定时巡检;

③ 线程池中一般全部设置为用户线程 ,不建议配置守护线程。
5、一句话总结setDaemon(true)设置守护线程,严格遵循先设置、后启动,启动后不可修改。

16.线程优先级有几级?(满分完整版+底层原理+面试坑点)

1、标准答案 :Java 线程优先级一共分为 1~10 级,一共10个等级。

2、三级划分(面试必背)

最低优先级(MIN_PRIORITY = 1) :后台辅助线程、低优先级巡检任务;

默认优先级(NORM_PRIORITY = 5) :主线程默认级别、普通业务线程;

最高优先级(MAX_PRIORITY = 10):紧急任务、高优先级心跳、监控线程。

3、核心底层原理

Java优先级仅仅是给操作系统的建议值,属于**概率优先级**:优先级越高,获取CPU时间片概率越大;优先级低,抢到CPU概率低。

4、代码修改优先级示例

java 复制代码
Thread thread = new Thread(()->{});
thread.setPriority(10); // 设置最高优先级
System.out.println(thread.getPriority()); // 获取优先级

5、高频面试坑点(必考)

Java优先级不保证执行顺序 ,仅仅是建议,不能依赖优先级做业务顺序;

② 主线程默认优先级为5;

③ 优先级范围必须在1~10之间,超出范围抛出IllegalArgumentException;

④ 不同操作系统对优先级适配不同,Linux系统优先级权重极低。
6、一句话总结优先级一共十级、默认五级、仅影响CPU抢占概率、不保证执行顺序。

17.什么是上下文切换?(满分完整版+底层原理+生产优化)

1、官方定义

CPU在多线程执行过程中,暂停当前正在运行的线程,保存当前线程运行状态(上下文) ,加载下一条线程的状态并执行,这个过程叫做线程上下文切换。
2、通俗比喻

CPU就是一个写字的人,线程就是作业;写完A作业一半,把A作业合上(保存上下文),拿出B作业继续写,来回切换的动作就是上下文切换。
3、切换保存的内容

程序计数器、虚拟机栈、寄存器状态、程序运行指针、内存地址标识。
4、触发上下文切换的常见场景(面试必考)

① 线程时间片耗尽,CPU强制切换;

② 线程主动阻塞:sleep()、wait()、join()、锁阻塞;

③ 线程优先级调度,高优先级抢占CPU;

④ GC垃圾回收触发线程停顿。
5、上下文切换优缺点

① 优点:实现多线程并发,最大化利用CPU资源;

② 缺点:切换极其耗性能 ,保存+恢复状态消耗CPU算力,频繁切换会导致系统吞吐量下降、CPU飙升。
6、生产如何减少上下文切换(线上优化)

① 合理设置线程数量,不要大量创建无效线程;

② 减少锁竞争,缩小锁范围,避免大量阻塞;

③ 禁止频繁使用sleep、wait;

④ CPU密集型任务线程数 = CPU核心数,减少抢占。
7、一句话总结上下文切换就是线程换人执行、保存现场、恢复现场;频繁切换是性能杀手。

18.线程死循环如何优雅停止?

使用 volatile 布尔标记位控制退出。

19.为什么不推荐 stop () 销毁线程?

强制终止,资源无法释放,数据不一致。

20.线程组作用?

批量管理一组线程,日常开发极少使用。

二、锁机制 Synchronized(21-40 题)

1.synchronized 是什么?(满分完整版+特性+通俗讲解)

1、官方定义

synchronized 是 Java 原生内置悲观锁 ,无需手动导入类、无需手动释放锁;属于排他锁、独占锁、可重入锁、非公平锁 。用于解决多线程共享资源竞争问题,保证线程安全。
2、核心四大作用(面试必背)

保证原子性 :同步代码块内代码不可拆分,一次性执行完毕;

保证可见性 :加锁强制刷新主内存,线程之间数据实时可见;

保证有序性 :禁止锁内部指令重排序;

解决并发竞争 :同一时刻只允许一条线程获取锁,串行执行。
3、三种使用方式

修饰普通方法(对象锁) :锁住当前 this 对象,不同实例互不影响;

修饰静态方法(类锁) :锁住 Class 类对象,所有实例共用一把锁;

修饰代码块(自定义锁) :锁住任意对象,锁范围最小、性能最优。
4、底层本质

底层依赖 对象头MarkWord + Monitor监视器锁 ,操作系统依赖mutex互斥量实现加锁。JDK1.6之后引入锁优化:偏向锁→轻量级锁→重量级锁,自动锁升级。
5、synchronized 核心特性

✅ 自动加锁、自动释放锁,无需人工干预;

✅ 异常自动释放锁,不会死锁卡死;

✅ 天然可重入,同一线程多次加锁不会阻塞;

❌ 不可中断、不能超时、不可手动灵活控制。
6、一句话总结synchronized是Java内置悲观独占锁,保证原子性、可见性、有序性;简单无脑、自动释放,底层依靠对象头完成锁升级。

2.synchronized 锁什么?(满分完整版+三类锁+代码区分+面试必考)

1、核心标准答案

synchronized 一共锁住三类对象:实例对象锁、类对象锁、自定义任意对象锁,对应三种使用方式,锁的对象不同,生效范围完全不同。

2、三类锁详细拆解(面试必背表格)

锁类型 锁住对象 生效范围 别称
普通实例方法 当前 this 实例对象 仅限制当前单个实例,不同实例互不阻塞 对象锁
静态方法 当前 Class 类对象 全局所有实例,所有对象共用一把锁 类锁
同步代码块 自定义任意对象 自定义控制锁范围,粒度最小、性能最优 自定义锁

3、三种锁代码直观演示

实例方法(锁this)

java 复制代码
// 锁住当前实例对象this
public synchronized void test(){}

静态方法(锁Class)

java 复制代码
// 锁住当前类的Class对象
public static synchronized void test(){}

同步代码块(锁自定义对象)

java 复制代码
// 可锁任意对象,推荐锁常量对象
synchronized (obj){
    // 同步代码
}

4、高频面试重难点

对象锁和类锁互不互斥 :一个加实例锁、一个加类锁,多线程可以同时执行,不会阻塞;

② 尽量使用同步代码块:缩小锁范围,减少阻塞时间,提升并发性能;

③ 不要锁字符串常量、不要锁空对象,容易出现隐形死锁;

④ 类锁作用于所有对象,全局互斥,适合静态共享资源。
5、一句话总结

普通方法锁实例、静态方法锁类、代码块锁任意对象;对象锁隔离实例,类锁全局隔离。

3.synchronized 底层原理?(满分完整版+三层底层+通俗易懂)

1、一句话极简背诵

底层依靠对象头MarkWord、Monitor监视器锁、操作系统Mutex互斥量 实现,JDK1.6优化锁升级机制。
2、三层底层结构(面试必背)

对象头 Mark Word(Java层)

每一个对象在内存中都有对象头,MarkWord占用内存大小:32位/64位。内部存放:锁标记位、偏向线程ID、GC分代年龄、哈希码。所有锁状态全部记录在MarkWord中,锁升级本质就是修改MarkWord二进制标识。

Monitor 监视器锁(JVM层)

每个对象绑定一把Monitor,底层由C++实现。Monitor包含:拥有线程、等待池、锁池、计数器 。未抢到锁进入锁池阻塞,调用wait进入等待池。

Mutex 互斥量(操作系统层)

重量级锁依赖操作系统底层互斥量,线程需要从用户态切换为内核态,阻塞挂起,开销极大。
3、加锁执行流程

线程访问同步代码 → 判断对象头锁标记 → 无竞争偏向锁、轻微竞争轻量级锁、激烈竞争膨胀为重量级锁 → 未抢到锁进入锁池阻塞 → 释放锁唤醒下一个线程。
4、JDK1.6 锁优化机制

引入自动锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁 ,只能升级、不能降级,目的是在不同竞争场景下最大化提升性能。
5、字节码底层指令

同步代码块底层依赖:monitorenter、monitorexit 两条字节码指令;同步方法不需要指令,依靠方法标识位判定。
6、高频面试坑点

① synchronized锁的是对象 ,不是代码;

② 偏向锁、轻量级锁在用户态执行,无系统阻塞;

③ 只有重量级锁才会触发用户态切换内核态,性能最差;

④ 代码块会出现两次monitorexit,保证异常情况下自动释放锁。
7、终极总结

MarkWord存锁状态、Monitor管控线程、Mutex实现阻塞;四层锁逐级升级,自适应优化性能。

4.Java 锁升级顺序?(满分完整版+升级流程+触发条件)

1、标准答案(必背)

JDK1.6 对 synchronized 进行锁优化,锁升级顺序:无锁 → 偏向锁 → 轻量级锁 → 重量级锁
2、核心硬性规则

锁只能升级,不能降级

② 升级目的:适配不同竞争场景,最大化提升 synchronized 执行性能;

③ 所有锁状态信息全部存放在对象头 MarkWord 中。
3、四级锁逐一层级详解

无锁 :对象刚创建,无任何线程竞争,MarkWord无锁标记;

偏向锁(单线程场景) :只有一条线程反复加锁,消除不必要CAS,偏向当前线程;

轻量级锁(轻微竞争) :多条线程交替抢锁、无阻塞,采用自旋CAS不停尝试抢锁;

重量级锁(激烈竞争) :大量线程并发争抢,自旋失败膨胀为重量级锁,底层依赖Monitor,线程阻塞挂起。
4、锁升级触发条件

① 无锁→偏向锁:第一次线程加锁;

② 偏向锁→轻量级锁:出现第二条线程竞争、打破偏向;

③ 轻量级锁→重量级锁:自旋次数超过阈值、竞争激烈、自旋消耗CPU过大。
5、高频面试坑点

① JDK1.6之前只有重量级锁,没有锁升级;

② 偏向锁默认延迟开启,程序启动前几秒默认轻量级锁;

③ 一旦膨胀为重量级锁,永远不会降级;

④ 偏向锁适合单线程、轻量级适合轻微竞争、重量级适合高并发激烈竞争。
6、一句话总结

无锁起步、单线程偏向、轻微竞争自旋、激烈竞争膨胀;只升不降,自适应优化性能。

5.偏向锁作用?

消除无竞争下锁 CAS 操作,提升单线程性能。

6.轻量级锁原理?

自旋 CAS 抢锁,用户态完成,不进入内核态。

7.自旋锁优缺点?

优点无阻塞;缺点占用 CPU,竞争激烈性能差。

8.重量级锁特点?

依赖内核态,线程阻塞挂起,开销大,竞争激烈稳定。

9.synchronized 可重入吗?

可重入,同一线程多次加锁不会死锁。

10synchronized 是否公平锁?

非公平锁,抢占式获取。

11.同步代码块和同步方法哪个效率高?(满分完整版+对比+开发规范)

1、标准答案同步代码块效率更高
2、核心根本原因

同步方法 :锁粒度极大,直接锁住整个方法、锁住当前this实例对象。哪怕方法内只有一小段代码需要同步,也会锁住全部代码,线程阻塞时间变长,并发性能差;

同步代码块 :可手动缩小锁范围,只包裹需要保证线程安全的共享资源代码,无关代码异步执行,减少线程阻塞抢占时间,吞吐量更高。
3、代码直观对比

java 复制代码
// 1、同步方法:锁范围大,锁住整个方法,效率低
public synchronized void update(){
    // 无关业务代码(不需要加锁)
    log.info("日志打印");
    // 仅这一行需要保证线程安全
    count++;
}

// 2、同步代码块:精准加锁,只锁关键代码,效率高
public void update(){
    log.info("日志打印"); // 无关代码无需加锁,并行执行
    // 仅锁住共享资源代码,粒度最小
    synchronized (this){
        count++;
    }
}

4、额外区别(面试加分)

① 同步方法:默认锁this(实例方法)、锁Class(静态方法),不能自定义锁对象

② 同步代码块:支持自定义锁对象,灵活可控,还可以优化为专用锁常量,避免锁膨胀;

③ 同步方法可读性简单、写法粗暴;同步代码块灵活、性能最优。
5、生产开发规范

① 业务开发优先使用同步代码块 ,缩小锁范围是并发优化基础;

② 禁止无脑给所有方法加synchronized,会造成大量无意义阻塞;

③ 简单极简工具类、短逻辑代码可使用同步方法。
6、一句话总结

锁范围越小性能越高,同步代码块粒度更小、灵活度更高,优于同步方法。

12.对象锁和类锁能否共存?(满分完整版+代码演示+面试高频坑点)

1、标准答案(必背)
可以共存、互不互斥、不会阻塞 。两者锁的对象不同,不是同一把锁,不存在锁竞争。
2、核心底层原因

对象锁 :锁住当前实例对象(this),锁的是堆中单个对象;

类锁 :锁住Class字节码对象,锁的是方法区唯一类对象;

两块不同的锁对象,互不干涉,所以一个线程获取对象锁、另一个线程获取类锁,可以同时执行
3、代码直观演示(证明共存不阻塞)

java 复制代码
public class LockDemo {
    // 类锁:静态同步方法
    public static synchronized void staticMethod(){
        while(true){
            System.out.println("我是类锁(静态锁)");
        }
    }
    // 对象锁:普通同步方法
    public synchronized void instanceMethod(){
        while(true){
            System.out.println("我是对象锁(实例锁)");
        }
    }
    public static void main(String[] args) {
        LockDemo demo = new LockDemo();
        // 开启线程1:执行类锁
        new Thread(LockDemo::staticMethod).start();
        // 开启线程2:执行对象锁
        new Thread(demo::instanceMethod).start();
    }
}

4、运行结果

两行代码交替同时打印 ,互相不阻塞,完美证明两把锁可以共存、无互斥关系。
5、面试超级易错坑点

① 很多求职者误以为synchronized全部互斥,只有锁同一个对象才会互斥

② 同一实例下:对象锁+类锁并行执行、互不阻塞;

③ 不同实例下:对象锁互不阻塞,类锁全局互斥;

④ 开发中不要同时混用对象锁和类锁,逻辑混乱极易引发并发bug。
6、一句话总结

锁对象不同就不互斥,对象锁锁实例、类锁锁Class,二者可以共存、并行执行。

13.synchronized 异常会释放锁吗?

会,线程抛出异常自动释放锁。

14.什么是锁消除?

JVM 编译优化,去除不可能存在竞争的锁。

15.什么是锁粗化?

连续多次加锁合并成一次,减少加锁次数。

16.偏向锁关闭场景?

多线程竞争激烈环境关闭偏向锁提升性能。

17.wait()、notify()、notifyAll() 区别?(满分完整版+表格+代码+面试必考)

1、核心共性

三者都是 Object 类的原生方法 ,必须在 synchronized 同步代码块/方法中使用,缺一报错非法监视器异常。作用于线程之间的等待与唤醒通信。

2、三者详细区别(面试必背表格)
方法名称作用功能锁行为线程去向 wait()让当前线程无限等待主动释放锁 进入对象等待池notify()随机唤醒一条等待线程执行完代码才释放锁从等待池移入锁池notifyAll()唤醒全部等待线程执行完代码才释放锁全部移入锁池竞争锁

3、逐方法深度讲解

wait() :主动放弃当前持有的锁,进入无限等待状态,释放CPU资源,等待被其他线程唤醒;分为无参(无限等待)、有参(限时等待)。

notify() :随机挑选一条处于等待池的线程唤醒,唤醒后不会立刻释放锁,必须执行完当前同步代码才释放。

notifyAll() :唤醒当前对象等待池中所有 等待线程,全部转移到锁池,重新竞争一把锁。
4、标准使用代码(生产者消费者极简示例)

java 复制代码
// 等待线程
synchronized (lock){
    while(条件不满足){
        lock.wait(); // 释放锁、进入等待
    }
}
// 唤醒线程
synchronized (lock){
    lock.notify();    // 随机唤醒一条
    // lock.notifyAll(); // 唤醒全部线程
}

5、高频面试坑点(必考)

① 三者必须在同步代码块执行,不加锁直接抛 IllegalMonitorStateException

② wait 立刻释放锁,notify/notifyAll 不会立刻释放锁,要等代码执行完毕;

③ notify 是随机唤醒,JVM不保证唤醒顺序;

④ 生产中尽量使用 notifyAll ,防止部分线程永久休眠(线程饿死)。
6、一句话总结

wait放手等待、notify随机唤醒、notifyAll全部唤醒;全部必须搭配synchronized使用。

18.notify 唤醒线程顺序?

无序,由 JVM 调度决定。

19.为什么 wait 必须放在循环里?

防止虚假唤醒。

20.synchronized 优缺点?(满分完整版+表格+生产对比)

1、一句话极简背诵

优点:简单无脑、自动加解锁、安全稳定;缺点:功能死板、竞争激烈阻塞严重、灵活性差。
2、详细优点(面试必背)

使用简单、底层原生 :Java内置关键字,无需手动创建对象,编码简洁;

自动释放锁 :代码执行完毕、程序异常,JVM都会自动释放锁,不会死锁;

天然锁特性齐全 :自带可重入、排他、原子性、可见性、有序性;

JVM持续优化 :JDK1.6引入偏向/轻量级/重量级锁,无竞争场景性能极高;

底层稳定无BUG :由JVM底层保障,无需人为维护,生产故障率极低。
3、详细缺点(面试必背)

灵活性极差 :不可中断、不能超时、无法手动释放锁;

高并发性能差 :线程竞争激烈直接膨胀为重量级锁,内核态阻塞,开销大;

非公平锁 :默认抢占机制,线程可能长期抢不到锁产生饥饿;

锁粒度死板 :方法锁只能锁this/class,无法精细化控制;

无法精准唤醒 :只能配合notify随机唤醒,不能指定唤醒某一条线程。
4、优缺点汇总表格

优点 缺点
Java 原生内置关键字,使用简单、编码便捷,无需手动导入额外类 灵活性极低,不可中断、不能设置超时,无法手动灵活控制锁的释放时机
自动加锁、自动释放锁,代码执行完毕或抛出异常都会自动释放锁,不会造成锁泄漏死锁 高并发竞争激烈场景下,会膨胀为重量级锁,线程内核态阻塞挂起,性能大幅下降
JVM 持续优化,低竞争、无并发场景下,偏向锁 / 轻量级锁性能表现优秀 默认非公平锁,无法修改为公平锁,存在线程长期抢不到锁的饥饿风险
底层由 JVM 保障,安全稳定,无内存泄漏风险,生产环境故障率极低 唤醒机制粗糙,仅支持 notify 随机唤醒、notifyAll 全部唤醒,无法精准唤醒指定线程

5、生产使用场景(加分考点)

✅ 适合:低并发、短代码、简单同步、少量竞争场景;

❌ 不适合:高并发、大量阻塞、需要超时、精准唤醒场景(改用ReentrantLock)。
6、一句话总结

低竞争无脑用synchronized,简单稳定零BUG;高并发必须换Lock,灵活可控性能更强。

三、JUC 显式锁 Lock(41-60 题)

1.Lock 和 synchronized 区别?(满分完整版+表格+代码+生产选型)

1、一句话极简背诵

synchronized自动加解锁、简单粗暴;Lock手动加解锁、灵活强悍。低并发用synchronized,高并发、复杂锁场景必须用Lock。
2、七大核心区别(面试必背表格)

对比维度 synchronized Lock(ReentrantLock)
底层归属 Java 原生关键字,JVM 底层实现 JDK 代码实现接口,属于 Java 代码层级
加锁方式 隐式锁,自动加锁、自动释放锁 显式锁,手动 lock () 加锁、unlock () 解锁
锁中断 不可中断,线程阻塞后会一直死等 可中断,lockInterruptibly () 可响应中断信号
超时机制 无超时机制,抢不到锁会永久阻塞 支持超时抢锁,tryLock (time) 可避免死锁
锁公平性 默认非公平锁,无法修改为公平锁 默认非公平锁,可手动传入 true 开启公平锁
唤醒机制 仅支持 notify 随机唤醒、notifyAll 全部唤醒,无法精准控制 搭配 Condition 可实现精准唤醒指定线程,分组管控
性能能力 低竞争场景性能优秀,高竞争激烈场景会膨胀为重量级锁,线程阻塞严重 高并发场景性能强悍,吞吐量更高,全程无内核态重量级阻塞

3、代码直观对比写法

synchronized 写法(简洁自动)

java 复制代码
// 无需手动解锁,异常自动释放锁
synchronized (obj){
    // 同步业务代码
}

Lock 写法(手动严谨)

java 复制代码
// 必须手动解锁,必须搭配finally防止死锁
Lock lock = new ReentrantLock();
lock.lock(); // 手动加锁
try {
    // 同步业务代码
}finally {
    lock.unlock(); // 强制手动释放锁
}

4、Lock 独有四大强悍功能(面试加分)

可中断加锁 :线程阻塞时可被中断,避免永久阻塞;

超时加锁 :tryLock(time)指定时间抢不到锁直接放弃,防止死锁;

公平锁实现 :new ReentrantLock(true)实现先来先排队;

精准唤醒 :多个Condition精准唤醒某一组线程,而不是随机唤醒。
5、底层原理区别

① synchronized:依赖对象头MarkWord+Monitor,JVM管控,自动锁升级;

② Lock:底层基于AQS抽象队列同步器 ,CAS+双向队列,代码层面自旋抢锁。
6、生产开发选型规范(必考)

✅ 选用synchronized:代码简单、低并发、短同步代码、无复杂逻辑;

✅ 选用Lock:高并发、大量阻塞、需要超时、公平锁、精准唤醒、读写分离。
7、高频面试坑点

① Lock必须在finally中解锁,否则异常卡死造成死锁;

② synchronized锁升级后无法降级,Lock全程自旋无重量级阻塞;

③ JDK1.8之后synchronized性能已经无限接近Lock,日常简单业务优先原生锁。
8、一句话总结

简单同步用synchronized省心,高并发复杂锁用Lock灵活;自动锁简单、手动锁强悍。

2.ReentrantLock 可重入原理?(满分完整版+源码+流程+通俗讲解)

1、极简定义

可重入指:同一线程可多次重复获取同一把锁,不会发生自我阻塞死锁。ReentrantLock 底层依靠 AQS 实现可重入机制。

2、底层核心三大要素(面试必背)

state 状态值 :int 类型,记录锁持有次数;无锁为0,每加锁一次+1,解锁一次-1;

exclusiveOwnerThread 独占线程 :记录当前持有锁的线程;

计数器机制:同一线程反复加锁,不断累加次数,解锁必须逐次清零,彻底归零才算真正释放锁。

3、完整执行流程

① 第一次加锁:state=0,当前线程抢占成功,state 修改为 1,标记持有线程;

② 同一线程再次加锁:判断当前线程等于持有线程,不阻塞、直接放行 ,state+1;

③ 其他线程加锁:线程不一致,进入双向队列阻塞排队;

④ 解锁流程:每解锁一次 state-1,必须 state=0 才彻底释放锁 ,清空持有线程。
4、关键源码片段(AQS内部判断) // 判断是否为当前持有线程

java 复制代码
if (current == exclusiveOwnerThread) {
    // 重入次数累加
    int nextc = c + acquires;
    setState(nextc);
    return true;
}

5、代码演示可重入特性 ReentrantLock lock = new ReentrantLock();

java 复制代码
lock.lock();  // 第一次加锁 state=1
lock.lock();  // 同一线程二次加锁 state=2,不会阻塞

lock.unlock();// 解锁一次 state=1
lock.unlock();// 彻底解锁 state=0

6、高频面试坑点

加锁次数必须和解锁次数一致 ,否则锁永远无法释放,造成死锁;

② synchronized 底层也有可重入,依靠 Monitor 计数器;

③ ReentrantLock 重入次数理论无上限,受 int 最大值限制;

④ 不可跨线程重入,只有当前持有锁线程能重复加锁。

7、一句话总结

依靠AQS的state计数器+持有线程标记,同线程累加次数实现重入;必须成对解锁清零,锁才算真正释放。

3.ReentrantLock 公平锁与非公平锁?(满分完整版+源码+对比+面试必考)

1、极简背诵定义

公平锁 :线程严格按照请求先后顺序排队,先来先获取锁,不允许插队,保证所有线程公平竞争;

非公平锁 :线程允许直接插队抢占锁,不遵循排队顺序,竞争激烈时可能出现线程饥饿,JUC默认非公平锁。
2、创建方式(源码构造方法)

java 复制代码
// 1、非公平锁(默认):无参构造,生产默认推荐
ReentrantLock lock = new ReentrantLock();
// 2、公平锁:传入布尔值true,手动开启公平机制
ReentrantLock fairLock = new ReentrantLock(true);

3、底层AQS实现原理

公平锁底层 :线程抢锁前,优先判断AQS双向阻塞队列是否存在排队线程;队列不为空则当前线程直接入队排队,禁止插队,严格保证先来后到;

非公平锁底层 :线程上来直接通过CAS暴力抢占锁,不判断排队队列;抢占失败后,再进入阻塞队列排队,允许新线程插队。
4、核心区别对比表格(面试必背)

对比维度 公平锁 非公平锁(默认)
抢占规则 严格排队、禁止插队、先来先服务 允许插队、CAS 抢占、暴力抢锁
线程饥饿 无线程饥饿,所有线程轮流获取锁 存在饥饿风险,老线程长期抢不过新线程
CPU 开销 开销大,频繁触发线程上下文切换 开销小,减少线程切换次数
吞吐量 吞吐量偏低,执行速度慢 吞吐量高,并发性能更强
底层逻辑 需判断队列前驱节点,逻辑繁琐 直接 CAS 抢锁,逻辑简单高效

5、生产开发适用场景

选用公平锁 :任务执行耗时均匀、对执行顺序有要求、不允许线程饥饿、业务优先级一致;

选用非公平锁(主流) :绝大多数业务场景、追求高吞吐量、追求高性能、无严格执行顺序要求。
6、高频面试坑点(必考)

① ReentrantLock 默认非公平锁 ,也是生产环境推荐使用;

② synchronized 底层永久是非公平锁,没有任何方式修改为公平锁;

③ 非公平锁性能更高的本质:减少线程阻塞唤醒、降低上下文切换开销;

④ 公平锁会遍历AQS队列判断前驱节点,逻辑复杂,资源消耗更高。
7、一句话总结公平锁排队不插队、无饥饿性能低;非公平锁抢占可插队、高性能默认首选。

4.tryLock () 作用?

尝试获取锁,失败不阻塞,可设置超时时间。

5.lockInterruptibly () 作用?

可响应中断的加锁方式。

6.读写锁 ReentrantReadWriteLock 特点?(满分完整版+规则+代码+面试坑点)

1、极简背诵定义

ReentrantReadWriteLock 是可重入、读写分离锁 ,分为**读锁(共享锁)**和**写锁(排他锁)**,专门适配**读多写少**业务场景,提高并发吞吐量。
2、四大互斥规则(面试必考核心)

读读共享 :多个线程同时加读锁,不互斥、不阻塞,并发最高;

读写互斥 :一个读、一个写,互相阻塞,防止数据脏读;

写写互斥 :多个线程写操作,串行执行,保证数据安全;

读锁共享、写锁独占 :读无锁开销、写保证安全。
3、底层原理

① 基于AQS实现,内部维护两把锁:ReadLock读锁、WriteLock写锁;

② 使用一个int类型state高位记录读锁数量、低位记录写锁重入次数;

③ 支持公平/非公平锁、支持可重入、支持锁降级。
4、标准代码模板(生产常用)

java 复制代码
// 创建读写锁
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
// 获取读锁
ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
// 获取写锁
ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();

// 读操作:共享无阻塞
readLock.lock();
try{
    // 查询、读取数据
}finally {
    readLock.unlock();
}

// 写操作:独占互斥
writeLock.lock();
try{
    // 修改、新增、删除数据
}finally {
    writeLock.unlock();
}

5、优缺点详解

优点 :读多写少场景极大提升并发性能、避免独占锁全程串行、锁粒度灵活;

缺点 :存在读锁饥饿 ,大量读线程霸占锁,写线程长期等待;不适合写多场景。
6、高频面试坑点

只支持锁降级,不支持锁升级 :写锁可以转读锁,读锁不能转写锁;

② 读锁是共享锁、写锁是排他锁;

③ 写锁完全互斥,串行执行,保证数据一致性;

④ 生产大量缓存框架底层都是读写锁(本地缓存、多级缓存)。
7、一句话总结读读共享、读写互斥、写写互斥;读多写少专用锁,读快写慢,支持锁降级。

7.什么是锁降级?

写锁降级为读锁,JUC 支持,不支持锁升级。

8.StampedLock 是什么?

乐观读写锁,比读写锁并发更高。

9.Condition 作用?(满分完整版+精准唤醒+完整代码+面试对比)

1、极简背诵定义

Condition 是 Lock 锁的线程等待唤醒工具类,替代 synchronized 的 wait/notify;

实现精准唤醒、分组唤醒线程,解决原生notify随机唤醒的弊端。

2、核心底层作用(面试必背)

精准唤醒 :可以指定唤醒某一组线程,不再随机唤醒;

分组阻塞 :一把锁创建多个Condition,不同线程绑定不同等待队列,互不干扰;

丰富功能 :支持超时等待、可中断等待、无参等待,比wait更强;

依赖Lock :必须配合ReentrantLock使用,不能单独创建。
3、核心常用方法

java 复制代码
// 线程等待,释放锁,进入等待队列
condition.await();
// 唤醒当前队列一条线程
condition.signal();
// 唤醒当前队列全部线程
condition.signalAll();

4、标准实操代码(精准唤醒演示)

java 复制代码
public class ConditionDemo {
    // 创建显式锁
    private static final ReentrantLock lock = new ReentrantLock();
    // 创建两组条件队列(分组)
    private static final Condition conditionA = lock.newCondition();
    private static final Condition conditionB = lock.newCondition();

    public static void main(String[] args) throws InterruptedException {
        // 线程A:阻塞在conditionA
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("线程A等待...");
                conditionA.await(); // A专属等待队列
                System.out.println("线程A被精准唤醒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }).start();

        // 线程B:阻塞在conditionB
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("线程B等待...");
                conditionB.await(); // B专属等待队列
                System.out.println("线程B被精准唤醒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }).start();

        Thread.sleep(1000);
        // 只精准唤醒线程A,不会影响线程B
        lock.lock();
        conditionA.signal();
        lock.unlock();
    }
}

5、Condition VS wait/notify(面试必考对比)

对比维度 wait/notify Condition
所属锁体系 synchronized 隐式锁 Lock 显式锁
唤醒方式 随机唤醒,无法指定唤醒目标线程 精准分组唤醒,可指定唤醒对应等待队列的线程
等待队列 单个全局等待队列,所有等待线程共用 可创建多个独立等待队列,不同业务场景线程分组隔离
功能强度 功能简单,仅支持基础等待 / 唤醒,无超时可控能力 功能丰富,支持超时等待、可中断等待、多种等待模式,灵活度更高

6、高频面试坑点

① Condition 必须配合 Lock 使用,不加锁直接报错;

② await() 会主动释放锁,和wait一致;

③ signal() 不会立刻释放锁,执行完代码才释放;

④ 生产中生产者消费者模型优先使用 Condition,精准控制线程。
7、一句话总结

Condition是Lock版的等待唤醒工具,多队列分组、精准唤醒,吊打原生notify随机唤醒。

10.Lock 如何避免死锁?(满分完整版+五种方案+代码实操+面试必考)

1、极简背诵定义

Lock 相比于synchronized自带多种**防死锁机制**,依靠手动可控加锁逻辑,从根源规避死锁;synchronized无法干预、只能阻塞死等,Lock可主动终止抢锁,避免线程永久卡死。
2、五大避免死锁核心手段(面试必背)

tryLock() 超时加锁(最核心) :指定时间抢不到锁直接放弃,不会永久阻塞,打破死锁请求保持条件;

lockInterruptibly() 可中断加锁 :阻塞中的线程可以被中断,主动跳出锁等待,破除死锁循环;

统一加锁顺序 :多把锁严格固定获取顺序,杜绝循环等待,最基础防死锁手段;

减少锁嵌套 :尽量不嵌套加锁,必须嵌套时控制层级,降低死锁概率;

finally强制解锁 :Lock必须在finally执行unlock,异常也不会造成锁不释放。
3、重点方法代码演示(防死锁核心代码)

tryLock超时避免永久阻塞

java 复制代码
// 5秒内抢不到锁直接放弃,不会死等
if (lock.tryLock(5, TimeUnit.SECONDS)){
    try{
        // 执行业务逻辑
    }finally {
        lock.unlock();
    }
}else{
    // 抢锁失败,直接退出,规避死锁
    System.out.println("抢锁失败,放弃执行");
}

lockInterruptibly可中断阻塞线程

java 复制代码
try {
    // 阻塞过程可被中断,跳出死锁等待
    lock.lockInterruptibly();
} catch (InterruptedException e) {
    // 收到中断信号,主动终止抢锁
    System.out.println("线程中断,终止抢锁");
}

4、Lock vs synchronized 死锁对比

对比维度 synchronized Lock(ReentrantLock)
阻塞特性 不可中断,永久死等 可中断、可超时、主动放弃
死锁规避 无任何规避手段 超时 + 中断双重防死锁
锁释放 自动释放,无法干预 手动释放,可控性强

5、高频面试坑点

① 死锁四大必要条件:互斥、请求保持、不可剥夺、循环等待 ;Lock主要破坏【请求保持、不可剥夺】两个条件;

② tryLock是生产最常用防死锁手段,适配多锁嵌套场景;

③ Lock必须手动unlock,若漏写解锁,依然会造成人为死锁;

④ synchronized只能靠代码顺序优化防死锁,无底层补救方法。
6、一句话总结

Lock依靠超时抢锁、中断抢锁、手动解锁破坏死锁条件;synchronized只能死等,无补救措施。

11.AQS 是什么?(满分完整版+架构+通俗讲解+面试必考)

1、极简背诵定义

AQS 全称 AbstractQueuedSynchronizer 抽象队列同步器 ,是 **JUC 并发包底层核心基石框架**。Java中所有显式锁、并发工具类底层全部依赖AQS,它封装了抢锁、排队、阻塞、唤醒通用逻辑,是锁的底层模板。
2、核心定位

AQS 是一套**通用锁模板**,Doug Lea 编写,把加锁、释放锁、线程排队全部封装,子类只需要简单重写少量方法,就能快速实现自定义锁。
3、AQS 三大核心组成(必背)

state 状态变量(核心灵魂) :int 类型,volatile修饰;

  • 0:无锁状态;

  • 大于0:线程持有锁,数值代表重入次数;

  • 不同工具类含义不同:ReentrantLock代表重入次数、CountDownLatch代表计数器、Semaphore代表许可数量。

双向FIFO阻塞队列 :存放抢锁失败的线程;

采用双向链表,包含head头节点、tail尾节点;未抢到锁的线程封装成Node节点入队休眠,节约CPU资源。

独占线程标记 :exclusiveOwnerThread,记录当前正在持有锁的线程,用于实现可重入判断。
4、两大锁模式(面试必考)

独占模式(排他锁) :同一时刻只能一个线程持有锁;代表类:ReentrantLock。

共享模式(共享锁) :多个线程可同时获取锁;代表类:CountDownLatch、Semaphore、读写锁。
5、底层完整执行流程

① 线程尝试通过CAS修改state抢锁;

② 抢锁成功:标记当前独占线程,执行业务;

③ 抢锁失败:封装成Node节点,加入双向阻塞队列;

④ 队列中线程挂起休眠,避免空转消耗CPU;

⑤ 持有锁线程释放锁,唤醒队列中下一个节点线程继续抢锁。
6、AQS 核心底层方法

java 复制代码
// 独占模式
acquire()    // 获取锁
release()     // 释放锁
// 共享模式
acquireShared()  // 获取共享锁
releaseShared() // 释放共享锁

7、高频面试坑点

① AQS 采用**模板方法设计模式**,通用逻辑父类实现,差异化逻辑子类重写;

② 阻塞队列中的线程是**挂起状态**,不消耗CPU,区别于自旋锁;

③ state必须用volatile修饰,保证多线程可见性;

④ synchronized底层没有使用AQS,只有JUC显式锁依赖AQS。

8、一句话总结

AQS是JUC锁底层通用模板,依靠state状态值+双向阻塞队列+独占线程,统一实现加锁、排队、唤醒逻辑,是所有显式锁的底层祖宗。

12.AQS 核心原理?(满分完整版+通俗拆解+执行流程+面试必考)

1、极简背诵定义

AQS核心原理:依靠state状态变量 + 双向FIFO阻塞队列 + 线程独占标记 ,配合CAS自旋,统一实现线程**加锁、排队、阻塞、唤醒**全套并发逻辑。
2、三大核心底层组件(必背)

State 状态变量(核心) :volatile int 修饰,同步器核心计数器;通过CAS修改值判断加锁/释放锁,不同锁含义不同:ReentrantLock为重入次数、读写锁拆分高低位、计数器为剩余次数。

双向阻塞队列 :抢锁失败的线程封装为Node节点,存入队列;双向链表结构、头尾指针,保证FIFO先进先出,线程入队后park挂起,不消耗CPU。

独占线程标记 :exclusiveOwnerThread,记录当前持有锁的线程,判定同线程重入、线程归属。
3、完整执行底层流程

① 线程CAS尝试修改state抢锁;

② 抢锁成功:修改state、标记独占线程,执行业务;

③ 抢锁失败:封装Node节点,尾插法进入双向队列;

④ 线程park休眠阻塞,让出CPU;

⑤ 锁释放:修改state,唤醒下一个有效后继节点;

⑥ 被唤醒线程再次竞争锁,循环往复。
4、两种工作模式

独占模式 :单线程持有锁,排他执行,如ReentrantLock;

共享模式 :多线程同时持有锁,如CountDownLatch、读写锁。
5、底层关键技术

CAS无锁自旋 :无竞争时不阻塞,轻量修改state;

LockSupport.park() :阻塞队列线程,休眠节省CPU;

模板方法模式 :父类通用排队逻辑,子类重写tryAcquire自定义抢锁。
6、高频面试坑点

① AQS队列内线程是挂起状态 ,不是自旋,不耗CPU;

② 队列节点采用尾插法 ,保证排队顺序;

③ 虚假唤醒:队列唤醒后仍需判断state状态,防止异常唤醒;

④ 只有JUC显式锁依赖AQS,synchronized底层无AQS。
7、一句话总结

一变量、一队列、一标记;CAS抢锁、队列排队、park休眠、有序唤醒,这就是AQS全部核心原理。

13.AQS 独占与共享模式?(满分完整版+通俗易懂+对比+面试必考)

1、极简背诵定义

AQS把所有锁分为两大类:独占模式(排他锁)共享模式(共享锁)

两种模式底层都是依靠state+双向队列,区别在于:同一时刻允许几条线程同时持有锁。
2、独占模式(Exclusive)详解

核心特点 :同一时刻只能有一条线程 持有锁,排他执行、互斥阻塞;

底层逻辑 :CAS修改state,state≠0代表锁被占用,其他线程全部入队阻塞;

代表类 :ReentrantLock、Synchronized底层、WriteLock写锁;

适用场景 :修改数据、增删改、强一致性、防止并发篡改。
3、共享模式(Shared)详解

核心特点 :同一时刻多条线程可以同时持有锁 ,并发执行、互不阻塞;

底层逻辑 :state代表许可数量/资源阈值,只要state>0,线程就能获取共享锁;

代表类 :CountDownLatch、Semaphore、ReadLock读锁;

适用场景 :查询数据、限流、批量等待、读多写少场景。
4、核心方法对照表(面试必背)

对比维度 独占模式(排他锁) 共享模式(共享锁)
持有线程数 同一时刻仅 1 条线程可持有锁,排他执行 允许多条线程同时持有锁,并发执行
核心方法 acquire() / release() acquireShared() / releaseShared()
state 变量含义 记录锁重入次数,0 = 无锁状态 记录剩余许可数量 / 计数器阈值
线程唤醒机制 仅唤醒队列中下一个有效后继节点线程,串行接力抢锁 向后传播唤醒,连续唤醒后续一批共享节点线程,批量放行
典型实现类 ReentrantLock、ReentrantReadWriteLock 写锁 ReentrantReadWriteLock 读锁、CountDownLatch、Semaphore

5、底层关键区别(面试加分)

独占模式 :唤醒线程时,只唤醒队列中下一个 有效线程,串行接力抢锁;

共享模式 :唤醒线程时,会向后传播唤醒 ,连续唤醒后面所有共享节点,实现批量放行;

③ 读写锁同时包含两种模式:写锁独占、读锁共享,这也是AQS高级混合用法。
6、高频面试坑点

① AQS本身不实现锁,只定义两种模式模板,子类按需实现;

② 独占锁保证数据强一致性,共享锁保证高并发吞吐量;

③ 共享模式下存在**无限向后唤醒**,提高并发放行效率;

④ 没有任何类同时大量混用两种模式,仅有读写锁特殊混合使用。
7、一句话总结

独占单线程排他执行、保证数据安全;共享多线程并行执行、提高并发吞吐量,二者构成AQS全部锁模型。

14.AQS 阻塞队列原理?(满分完整版+通俗易懂+流程拆解+面试必考)

1、极简背诵定义

AQS阻塞队列本质是双向Node节点链表(FIFO) ,专门存放抢锁失败的线程;通过尾插法入队、前驱唤醒后继,配合park休眠,实现线程有序排队、无CPU空转,是AQS管控阻塞线程的核心容器。
2、队列核心组成(必背)

Node节点 :每一个抢锁失败的线程被封装为Node,存储线程引用、等待状态、前后指针;

头尾指针 :head头节点、tail尾节点,默认初始化空哨兵节点,避免链表判空;

等待状态waitStatus :标记节点状态(等待、取消、唤醒),控制线程流转;

双向指针 :prev前驱、next后继,形成双向链表,方便回溯遍历、精准唤醒。
3、四大节点状态(面试冷门加分)

SIGNAL(-1) :后继节点需要唤醒,前驱节点释放锁后唤醒下一个;

CANCELLED(1) :线程超时/中断,节点取消排队,永久失效;

CONDITION(-2) :线程在Condition条件队列等待;

0 :节点初始默认状态。
4、完整入队执行流程(抢锁失败)

① 线程CAS抢锁失败,不反复无效自旋;

② 封装为Node节点,通过CAS尾插法 插入链表尾部;

③ 修改前驱节点状态为SIGNAL,标记需要唤醒;

④ 调用LockSupport.park(),线程挂起休眠、释放CPU;

⑤ 进入阻塞队列安静排队,不消耗系统资源。
5、完整出队执行流程(锁释放)

① 持有锁的线程修改state、释放锁;

② head头节点向后寻找第一个有效后继节点;

③ 调用unpark()唤醒该节点线程;

④ 被唤醒线程重新CAS抢占锁;

⑤ 抢锁成功后清空节点、移动头指针,废弃无效节点。
6、关键底层规则(面试必考)

哨兵节点机制 :队列初始化就创建空head节点,简化头尾判断逻辑;

前驱唤醒后继 :永远由前一个节点唤醒下一个节点,不会跳跃唤醒;

剔除取消节点 :唤醒过程中自动清理超时、中断的无效节点;

非公平锁插队 :新线程可直接CAS抢锁,不用入队排队。
7、高频面试坑点

① 队列线程是park挂起状态 ,不是自旋,零CPU消耗;

② 必须双向链表:方便删除中间取消的节点,单向链表无法回溯;

③ 头节点永远是无效哨兵,真正排队线程从第二个节点开始;

④ 共享模式会链式唤醒,独占模式只唤醒单个后继节点。
8、一句话总结

双向链表存线程、尾插排队、前驱唤醒后继、park休眠省CPU;哨兵节点简化逻辑,自动剔除无效节点,这就是AQS阻塞队列全部原理。

15.volatile 关键字作用?(满分完整版+三大特性+底层原理+代码+面试必考)

1、极简背诵定义

volatile 是 Java 轻量级同步关键字,无锁、不阻塞 ;专门解决多线程内存不可见、指令重排问题,不保证原子性,是轻量级并发安全手段,底层依托内存屏障实现。
2、三大核心作用(面试必背、必考)

保证可见性 :修饰变量,一条线程修改后,强制刷新主内存,其他线程立刻感知最新值,消除工作内存缓存差异;

禁止指令重排序 :底层添加内存屏障,禁止编译器、CPU对代码乱序优化,保证代码执行有序;

不保证原子性:只能保证单次读写可见,复合操作(i++、自增累加)无法保证线程安全。

3、底层实现原理

内存可见性 :volatile变量修改后,触发CPU缓存失效,强制写入主内存,其他线程缓存失效重新读取;

内存屏障 :插入LoadLoad、StoreStore屏障,禁止上下代码指令交换位置,杜绝重排序;

无锁机制 :不占用锁资源、不阻塞线程,纯硬件指令优化,性能远超synchronized。
4、代码演示(可见性经典案例)

java 复制代码
// 不加volatile:主线程永远死循环,看不到修改后flag
// 加volatile:主线程立刻感知,跳出循环
private static volatile boolean flag = true;
public static void main(String[] args) throws InterruptedException {
    new Thread(() -> {
        while (flag){
            // 空循环
        }
        System.out.println("线程感知变量变化,退出循环");
    }).start();
    Thread.sleep(2000);
    flag = false; // 修改变量
}

5、为什么不保证原子性?(高频追问)

原子性需要:读取→修改→写入三步操作;

volatile只能保证单次读写可见,中间修改步骤无法加锁,多线程并发修改会出现数据覆盖。典型例子:volatile修饰int,i++依旧线程不安全。

6、volatile 与 synchronized 区别(必背表格)

对比维度 volatile synchronized
锁级别 轻量级无锁 重量级独占锁
阻塞特性 不阻塞、无上下文切换 阻塞线程、开销大
保障能力 可见性、有序性,无原子性 原子性、可见性、有序性
适用场景 状态标记、简单变量同步 同步代码块、复合操作
底层原理 内存屏障 + 缓存失效 Monitor 锁 + 对象头

7、高频面试坑点

① volatile 绝对不能替代锁 ,无法解决自增、复合运算线程安全;

② JDK1.5之后volatile才真正生效,修复早期内存模型漏洞;

③ 不修饰方法、不修饰常量,只修饰成员变量

④ 禁止重排序可以解决DCL单例模式空指针问题。

8、一句话总结

volatile保证可见性、禁止重排、不保证原子;轻量无锁性能高,只做状态标记,不做计算修改。

16.volatile 适用场景?(满分完整版+四大实战场景+代码+禁忌+面试必考)

1、极简背诵定义

volatile 适合简单轻量、无复合计算、仅读写标记 的场景;依靠可见性+禁止重排序特性,在不使用锁的前提下保证线程感知变量变化,**绝对不能用于多线程计算、数据累加**。
2、四大生产常用场景(面试必背)

状态标记位(最常用)

用于线程启停、开关控制,多线程只读、单线程修改;例如线程终止标记、服务启停开关、全局判断标识。

java 复制代码
// 优雅终止线程标记位
private static volatile boolean running = true;

双重校验锁DCL单例模式

禁止指令重排序,解决对象初始化空指针问题,保证单例对象创建有序,是volatile经典高频面试场景。

读写分离、简单共享变量

一写多读场景:一条线程修改、多条线程读取;保证读线程实时拿到最新值,无需加锁,性能极高。

底层并发工具修饰

JUC底层大量使用:AQS的state、Atomic原子类value、线程池变量,依靠volatile保证内存可见。
3、经典实战代码(DCL单例)

java 复制代码
public class Singleton {
    // volatile禁止指令重排,防止半初始化对象溢出
    private static volatile Singleton instance;
    private Singleton(){}
    public static Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

4、绝对不适用场景(面试大坑)

不能用于复合运算 :i++、自增、加减乘除,无法保证原子性;

不能作为共享计数器 :多线程同时修改会数据覆盖;

不能解决并发修改冲突 :仅保证可见,无互斥能力。
5、生产开发硬性规范

✅ 推荐:开关标记、状态位、配置热更新、一写多读;

❌ 禁止:数值计算、累加统计、多线程同时修改。
6、高频面试坑点

① volatile只能修饰成员变量,局部变量无效;

② 仅保证单次读写原子性,复合操作一律不安全;

③ 高并发修改场景必须改用synchronized/CAS原子类;

④ DCL单例必须加volatile,防止JVM指令重排造成空指针。
7、一句话总结

只做标记不做计算、只做读取不做修改;一写多读专用,杜绝复合运算,这就是volatile使用边界。

17.CAS 原理?(满分完整版+通俗拆解+源码+流程+面试必考)

1、极简背诵定义

CAS 全称 Compare And Swap(比较并交换) ,是Java无锁乐观锁 ;不加锁、不阻塞、依靠硬件指令,自旋比较内存值,实现线程安全修改,底层依赖Unsafe类,是AQS、原子类的底层核心。
2、CAS三大核心参数(必背)

内存地址V :要修改的共享变量在内存中的真实地址;

预期旧值A :线程读取到的当前变量旧值;

更新新值B :线程想要修改写入的目标新值。
3、底层执行流程(通俗易懂)

① 线程从主内存读取变量,记录为预期旧值A;

② CAS判断:内存地址V中的真实值 是否等于 预期旧值A;

相等:无其他线程修改,直接交换,写入新值B,修改成功

不相等:被其他线程篡改,本次修改失败,重新自旋重试

⑤ 循环反复判断,直到修改成功为止。
4、底层源码与硬件原理

① Java层面:调用 Unsafe 类native本地方法,无Java实现源码;

② JVM层面:封装汇编指令;

③ 硬件层面:依靠 CPU cmpxchg 原子指令,硬件级别保证操作不可拆分;

④ 单核/多核CPU均会加总线锁,杜绝并发篡改,保证原子性。
5、简易代码演示(逻辑模拟)

java 复制代码
// CAS伪代码逻辑,通俗易懂
public boolean CAS(V内存地址, A预期旧值, B新值){
    if(内存真实值 == A){
        内存真实值 = B;
        return true; // 修改成功
    }
    return false; // 被篡改,修改失败
}

6、CAS核心优点

无锁无阻塞 :不触发线程阻塞、无上下文切换;

性能极高 :用户态自旋,开销远小于synchronized重量级锁;

乐观并发 :默认竞争不激烈,适合轻量并发场景。
7、高频面试坑点

① CAS只能保证单个变量原子性 ,无法保证多行代码复合原子性;

② CAS是乐观锁 ,synchronized是悲观锁;

③ 自旋空转消耗CPU,竞争极端激烈场景性能不如重量级锁;

④ 底层必须依赖Unsafe类,无法手动直接调用。
8、一句话总结

拿旧值比对内存,相等就改、不等重试;硬件指令保证原子、无锁自旋高性能,这就是CAS全部原理。

18.CAS 三大问题?(满分完整版+通俗拆解+代码+解决方案+面试必考)

1、极简背诵定义

CAS虽然无锁高性能,但是存在ABA问题、循环自旋空转、只能单变量原子性 三大硬伤;生产必须针对性解决,否则出现并发BUG。
2、第一大问题:ABA问题(最经典面试题)

问题原理 :线程A读取旧值A,还未CAS修改;线程B先把A改成B、又改回A;线程A判定数值没变,修改成功,忽略中间篡改过程 ,产生数据隐患。

通俗比喻 :钱包100元→被人拿走变0→又补回100元;你只看到最后还是100,不知道中间被动过手脚。

代码演示隐患 :普通AtomicInteger无法识别ABA篡改。

解决方案添加版本号/时间戳 ;使用带标记的引用类:AtomicStampedReference。
3、第二大问题:循环自旋空转(CPU消耗)

问题原理 :竞争激烈时,CAS一直修改失败,无限循环重试;线程不断空转CAS,占用CPU资源,CPU飙升。

痛点场景 :大量线程同时修改同一个变量,自旋次数暴增,性能碾压重量级锁。

解决方案

✅ 增加自旋次数上限,超限改用重量级锁;

✅ JDK自适应自旋,控制循环次数;

✅ 高并发竞争场景直接使用synchronized、Lock锁。
4、第三大问题:只能保证单个变量原子性

问题原理 :CAS硬件指令只能针对一个内存地址 做原子修改;无法同时操作多个变量、无法保证多行代码复合原子性。

业务痛点 :同时修改两个共享变量,CAS无法保证数据一致性。

解决方案 :多行复合操作、多变量修改,必须使用悲观锁synchronized/Lock
5、三大问题+对应解决方案汇总表(必背)

存在问题 产生原因 解决方案
ABA 问题 数值反复修改、无修改记录版本号 添加版本号 / 时间戳(使用 AtomicStampedReference)
循环自旋空转 竞争激烈、修改失败无限重试 限制自旋次数上限,竞争激烈场景改用悲观锁
单变量原子性限制 硬件 CAS 指令仅能锁定单个内存地址 多变量 / 复合操作场景,改用 Lock/synchronized 悲观锁

6、高频面试坑点

① CAS不能杜绝ABA ,普通原子类默认存在ABA漏洞;

② 自旋空转是CPU密集消耗,高并发慎用CAS;

③ CAS只能保证行内原子,不能替代锁做复杂业务;

④ JUC中AQS、原子类均规避了CAS缺陷,做了优化封装。
7、一句话总结

ABA要看版本、自旋怕高并发、只能改单变量;三大缺陷各有解法,复杂业务放弃CAS改用锁。

19.ABA 问题如何解决?(满分完整版+双代码演示+通俗详解+面试必考)

1、极简背诵定义

ABA核心解决思路:不比较数值,比较修改版本 ;给数据添加版本号/时间戳,记录每一次修改记录,哪怕数值变回原值,版本号不可逆递增,彻底规避ABA篡改漏洞。
2、原生缺陷:普通原子类存在ABA漏洞

AtomicInteger、AtomicLong 仅对比数值,无修改记录;数值A→B→A,线程无法识别中间篡改,判定无修改,存在安全隐患。
3、官方解决方案(面试必背)

AtomicStampedReference(主流) :维护引用值+版本号 ,每次修改版本号自增,比对数值+版本号双重校验;

AtomicMarkableReference :维护引用值+布尔标记 ,仅标记是否修改,不记录修改次数;

自定义时间戳 :业务中给共享变量附加修改时间戳,替代版本号,逻辑一致。
4、底层解决原理

普通CAS:只对比【内存值、预期值】;

改良CAS:同时对比【内存值、预期值、内存版本、预期版本】;

只有数值+版本号全部一致 ,才判定无篡改,允许修改,完美杜绝ABA。
5、代码演示1:普通AtomicInteger产生ABA问题

java 复制代码
// 普通原子类:存在ABA漏洞
AtomicInteger atomic = new AtomicInteger(100);
new Thread(()->{
    // 1、读取初始值100
 int old = atomic.get();
    try {Thread.sleep(2000);} catch (Exception e) {}
    // 3、时隔两秒,尝试修改,依旧修改成功(存在隐患)
    atomic.compareAndSet(old, 200);
}).start();
new Thread(()->{
    // 2、中间篡改:100→0→100,数值还原
    atomic.compareAndSet(100, 0);
    atomic.compareAndSet(0, 100);
}).start();

6、代码演示2:AtomicStampedReference彻底解决ABA

java 复制代码
// 带版本号原子引用:杜绝ABA
// 参数:初始值、初始版本号
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 1);
new Thread(()->{
    // 获取当前值+当前版本
    int oldValue = ref.getReference();
    int oldStamp = ref.getStamp();
    try {Thread.sleep(2000);} catch (Exception e) {}
    // 数值相同、版本不同,修改失败,拦截ABA篡改
    boolean success = ref.compareAndSet(oldValue,200,oldStamp,oldStamp+1);
    System.out.println("是否修改成功:"+success); // 输出false
}).start();
new Thread(()->{
    // 两次修改,版本号持续递增
    ref.compareAndSet(100,0,ref.getStamp(),ref.getStamp()+1);
    ref.compareAndSet(0,100,ref.getStamp(),ref.getStamp()+1);
}).start();

7、两个工具类区别(面试冷门加分)

对比维度 AtomicStampedReference AtomicMarkableReference
核心标记类型 整型版本号(stamp),每次修改版本号不可逆递增 布尔标记(mark),仅记录是否被修改过,无次数统计
修改记录能力 完整记录每一次修改的版本迭代,可精准统计修改次数、追溯修改轨迹 仅能标记是否发生过修改,无法记录修改次数、无法追溯多次修改的过程
ABA 问题解决能力 彻底解决 ABA 问题,哪怕数值变回原值,版本号不同也能识别中间篡改过程 仅能解决单次 ABA 问题,无法应对多次来回修改的复杂 ABA 场景
适用场景 需要精准统计修改次数、追溯修改轨迹、严格校验数据完整性的场景,如库存扣减、订单状态流转 仅需判断数据是否被修改过、无需关注修改次数的简单场景,如开关标记、状态校验
底层实现复杂度 双参数(引用 + 版本号)CAS 操作,逻辑略复杂,需同时校验引用和版本号 单参数(引用 + 布尔标记)CAS 操作,逻辑更简单,仅需校验引用和标记位
性能开销 版本号递增 + 双值校验,自旋成功率略低,性能开销略高 布尔标记 + 单值校验,自旋成功率更高,性能开销更低

8、高频面试坑点

① JDK原生普通原子类全部无法规避ABA ,必须手动改用带版本原子引用;

② 版本号只能递增、不能回退,保证修改记录不可逆;

③ AtomicStampedReference自旋成功率更低,性能略低于普通原子类;

④ 业务简单场景可用时间戳替代版本号,逻辑一致。
9、一句话总结

ABA问题本质是只看结果不看过程;加版本号双重校验,记录修改轨迹,是最简单通用的解决方案。

20.原子类原理?(满分完整版+分类+底层+代码+面试必考)

1、极简背诵定义

原子类是JUC封装的无锁线程安全工具类 ,底层核心依靠 CAS + volatile + Unsafe 实现;不加锁、不阻塞,保证多线程下变量操作原子性,性能远超synchronized同步锁,专门解决基础变量并发安全问题。
2、底层三大核心支柱(面试必背)

CAS无锁自旋 :硬件级比较交换,失败循环重试,替代重量级锁实现原子修改;

volatile 可见性 :修饰共享变量,保证多线程内存可见、禁止指令重排;

Unsafe 底层类 :提供native本地方法,直接操作内存地址,调用CPU原子指令。
3、原子类四大分类(面试思维导图)

基础数据原子类 :AtomicInteger、AtomicLong、AtomicBoolean;适配普通数值计算;

引用类型原子类 :AtomicReference、AtomicStampedReference;解决对象修改、ABA问题;

数组原子类 :AtomicIntegerArray、AtomicLongArray;保证数组内元素线程安全;

累加器增强类 :LongAdder、DoubleAdder;高并发计数,分段锁优化,性能碾压普通原子类。
4、底层执行流程(通俗易懂)

① 通过Unsafe获取变量内存偏移地址;

② volatile修饰变量,实时读取主内存最新值;

③ CAS比对内存旧值,一致则原子修改;

④ 修改失败无限自旋重试,直到修改成功;

⑤ 全程无锁、无阻塞、用户态执行。
5、基础原子类代码演示

java 复制代码
// 普通int多线程不安全,AtomicInteger保证原子性
AtomicInteger count = new AtomicInteger(0);
// 自增方法,底层CAS自旋
count.incrementAndGet();
// 底层源码逻辑:CAS循环比对修改

6、普通原子类 VS LongAdder(高频面试对比)

对比维度 普通原子类(AtomicInteger/AtomicLong) LongAdder
底层原理 单 CAS 自旋修改,全程对单个内存地址自旋抢锁 分段累加、分散竞争,将压力拆分到多个单元格,降低锁冲突
并发性能 低,高并发场景下大量线程自旋空转,CPU 消耗高 极高,高并发下拆分竞争压力,吞吐量碾压普通原子类
ABA 问题 存在 ABA 漏洞,数值来回修改无法识别中间篡改过程 无 ABA 问题,仅做数值累加,无版本号校验需求,天然规避 ABA 风险
适用场景 普通低并发计数、轻量数值修改场景 超高并发统计、接口流量统计、PV/UV 统计、大流量业务计数场景

7、高频面试坑点(必考)

① 原子类 只能保证单个变量原子性 ,无法解决多行代码复合原子性;

② 普通原子类存在ABA问题,必须使用带版本号引用类规避;

③ 高并发计数优先 LongAdder ,放弃AtomicInteger,减少CPU空转;

④ 所有原子类底层都不依赖锁,属于乐观无锁并发;

⑤ JDK1.8新增Adder累加器,专门优化高并发竞争痛点。

8、一句话总结

原子类依托CAS自旋+volatile可见性+Unsafe内存操作;无锁安全、轻量高效,普通计数用Atomic,超高并发用LongAdder。

四、并发工具类(61-80 题)

1.CountDownLatch 作用?(满分完整版+底层+代码+对比+面试必考)

1、极简背诵定义

CountDownLatch 是一次性递减计数器,属于AQS共享模式实现;允许**一条主线程等待多条子线程全部执行完毕**,再继续向下执行,起到线程批量等待、汇总结果作用,执行完毕不可重置复用。

2、底层核心原理(面试必背)

① 基于AQS共享模式,state作为计数器 ,初始化设定计数次数;

② countDown():CAS递减state计数器,计数减一;

③ await():阻塞主线程,直到state递减为0,批量唤醒等待主线程;

④ 底层使用LockSupport.park()阻塞、unpark()唤醒,无锁阻塞、性能高。

3、标准实操代码(生产通用模板)

java 复制代码
// 初始化计数器,设定3个子线程
CountDownLatch latch = new CountDownLatch(3);
for (int i = 1; i <= 3; i++) {
    new Thread(()->{
        try {
            // 子线程执行业务
            System.out.println(Thread.currentThread().getName()+"执行任务");
        } finally {
            // 计数器必须放在finally,保证一定递减
            latch.countDown();
        }
    },"线程"+i).start();
}
// 主线程阻塞,等待全部子线程完成
latch.await();
System.out.println("所有子线程执行完毕,主线程汇总执行");

4、三大核心常用方法

CountDownLatch(int count) :构造方法,初始化计数器次数;

countDown() :计数器减一,线程执行完调用;

await() :阻塞当前线程,等待计数器归零;

await(time,unit) :限时等待,超时自动放行,防止永久阻塞。
5、生产真实使用场景(面试加分)

并行任务汇总 :多线程拆分查询数据,全部查询完统一组装返回;

服务启动预热 :项目启动加载多个资源,全部加载完再对外提供服务;

压测并发模拟:统一放行大量线程,模拟高并发同时请求。

6、CountDownLatch VS CyclicBarrier(高频对比)

对比维度 CountDownLatch CyclicBarrier
复用性 一次性使用、不可重置 循环复用、可重复拦截
等待对象 主线程等待子线程全部执行完成 子线程之间互相等待,集齐指定数量线程
核心逻辑 计数器递减,计数归零自动放行 栅栏拦截,集齐线程后统一批量放行
适用场景 多任务拆分汇总、主线程等待子线程结果 多线程集合同步执行、分阶段并行任务、并发压测

7、高频面试坑点(必考)

① 计数器必须放到finally代码块 ,防止异常导致计数器无法递减、主线程永久阻塞;

② 只能一次性使用,计数归零后无法重置;

③ await()不会抛出中断以外异常,阻塞期间可响应中断;

④ 不适合无限任务,一定要搭配超时await防止卡死。
8、一句话总结

减法计数器、一次性不可复用;子线程逐个递减、主线程阻塞等待,集齐全部任务再放行,做汇总收尾专用。

2.CyclicBarrier 作用?(满分完整版+原理+代码+对比+面试必考)

1、极简背诵定义

CyclicBarrier 是循环栅栏、可复用计数器 ,允许一组线程互相等待,全部到达栅栏屏障点后,再统一同时放行执行;执行完毕计数器自动重置,可循环重复使用。
2、底层核心原理

① 基于AQS共享模式实现,内部维护计数阈值、等待线程数;

② 每一条线程执行到await(),线程阻塞、计数器递减;

③ 所有线程全部抵达栅栏(计数归零),批量唤醒所有阻塞线程,统一并发执行;

④ 支持栅栏触发后执行优先任务,支持重置复用。
3、标准实操代码

java 复制代码
// 设置栅栏阈值:3个线程集齐统一执行
CyclicBarrier barrier = new CyclicBarrier(3,()-> System.out.println("全部线程就位,统一发车执行"));
for (int i = 1; i <= 3; i++) {
    new Thread(()->{
        try {
            System.out.println(Thread.currentThread().getName()+"到达栅栏,等待集结");
            // 线程阻塞,等待集齐所有线程
            barrier.await();
            System.out.println(Thread.currentThread().getName()+"统一并发执行任务");
        } catch (Exception e) {
            e.printStackTrace();        
        }    
    },"线程"+i).start(); 
}

4、核心常用方法

CyclicBarrier(int parties) :设置栅栏拦截线程数量;

await() :当前线程到达屏障点,阻塞等待集结;

reset() :手动重置栅栏,恢复初始计数,可二次使用。
5、生产适用场景

多线程集结同步 :多人任务集齐后统一执行,模拟并发压测;

分阶段执行任务 :第一阶段全部执行完,再开启第二阶段,分段管控;

批量并行计算 :多线程拆分计算,全部完成后统一进入下一流程。
6、CyclicBarrier 与 CountDownLatch 核心区别(面试必背)对比维度

对比维度 CountDownLatch CyclicBarrier
计数器类型 减法计数器,计数不可逆,只能从初始值递减到 0 加法计数器,可重置复用,计数达到阈值后自动重置
复用性 一次性使用,计数归零后无法重置,不可二次使用 循环复用,多次拦截线程,放行后自动重置初始计数,可重复使用
等待对象 主线程阻塞等待,等待所有子线程执行完成 子线程之间互相阻塞等待,所有线程互相等待集结
触发放行时机 计数器递减至 0 时,自动唤醒阻塞的主线程 集齐指定数量的线程后,统一批量唤醒所有阻塞线程
核心设计目标 主线程等待多线程任务全部完成,做汇总收尾工作 多线程之间同步集结,统一开始执行后续任务,适配分阶段并行执行
典型适用场景 多任务拆分汇总、服务启动预热、主线程等待子线程结果 多线程集合同步执行、分阶段并行任务、并发压测、数据分片计算

7、高频面试坑点

① 任意线程中断,栅栏直接破损,剩余线程抛出异常;

② 必须凑齐设定线程数才放行,否则永久阻塞;

③ 支持栅栏回调任务,集齐线程后优先执行回调逻辑;

④ 适合分段同步任务,不适合一次性汇总任务。
8、一句话总结

CyclicBarrier是可循环栅栏,线程互相等待、集齐统一发车、重复复用;适合多线程集结同步、分段并行执行。

3.CyclicBarrier 作用?(满分完整版+企业实战代码+业务场景+坑点)

1、极简背诵定义

CyclicBarrier 是可循环复用的线程栅栏 ,底层基于AQS共享模式;让一组线程互相等待,全部抵达屏障点后统一批量放行,支持循环复用、栅栏回调任务,适合多线程分阶段同步执行。
2、核心关键特性(面试必背)

可循环复用 :计数归零后自动重置,反复使用;

线程互相等待 :子线程之间阻塞等待,集齐指定数量线程才放行;

栅栏回调 :所有线程抵达屏障后,优先执行自定义回调任务;

阻塞机制 :底层LockSupport.park()阻塞,无锁阻塞、性能高。
3、企业实战业务代码(生产可直接复用)

业务场景:批量拆分查询数据库,多线程并行查询,全部查询完成后统一汇总数据,分阶段执行。

java 复制代码
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * CyclicBarrier 企业实战:批量数据分片查询汇总
 * 场景:千万级数据拆分多线程查询,集齐结果统一组装
 */
public class CyclicBarrierBusinessDemo {
    // 定义分片查询线程数
    private static final int THREAD_NUM = 4;
    // 创建栅栏+集齐线程后的回调汇总任务
    private static final CyclicBarrier BARRIER = new CyclicBarrier(THREAD_NUM, () -> {
        // 所有分片查询完毕,执行汇总任务
        System.out.println("✅ 所有分片查询完成,开始统一汇总组装数据");
    });
    // 存储分片查询结果
    private static final List<String> RESULT_LIST = new ArrayList<>();

    public static void main(String[] args) {
        // 创建固定线程池
        ExecutorService pool = Executors.newFixedThreadPool(THREAD_NUM);
        // 模拟4个分片查询任务
        for (int i = 1; i <= THREAD_NUM; i++) {
            int shard = i;
            pool.execute(() -> {
                try {
                    // 1、模拟分片数据库查询
                    System.out.printf("线程%s:查询第%d分片数据...%n",Thread.currentThread().getName(),shard);
                    Thread.sleep(800); // 模拟IO查询耗时
                    RESULT_LIST.add("分片"+shard+"查询数据");
                    System.out.printf("线程%s:第%d分片查询完成,抵达栅栏等待集结%n",Thread.currentThread().getName(),shard);

                    // 2、抵达屏障点,阻塞等待所有线程完成
                    BARRIER.await();

                    // 3、全部集齐后,统一执行后续业务
                    System.out.printf("线程%s:开始执行后续同步业务%n",Thread.currentThread().getName());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        // 关闭线程池
        pool.shutdown();
    }
}

4、核心常用方法 ① CyclicBarrier(int parties):初始化拦截线程数量;

② await():线程到达屏障,阻塞等待集结;

③ reset():手动重置栅栏,恢复初始状态;

④ getNumberWaiting():获取当前阻塞等待的线程数。
5、真实生产落地场景

数据分片计算 :大数据拆分多线程并行计算,全部完成后汇总;

接口并发压测 :所有请求线程集结完毕,统一同时发起请求;

分阶段任务 :流程分段执行,上一阶段全部完成再进入下一阶段;

多服务同步初始化:多个资源加载完毕,再启动对外服务。

6、高频面试坑点(必考)

① 任意线程中断,栅栏直接破损,其余线程抛出BrokenBarrierException;

② 必须凑齐设定线程数才放行,无超时会永久阻塞,生产建议加超时await(time);

③ 回调任务由最后抵达的线程 执行,并非单独线程;

④ 区别CountDownLatch:CyclicBarrier线程互相等、可复用;后者主线程等子线程、一次性。
7、一句话总结

CyclicBarrier循环栅栏,多线程集结排队、统一发车、分段执行;大数据分片、并发压测首选,可循环复用是核心亮点。

4.Exchanger 作用?(满分完整版+企业实战代码+底层原理+面试坑点)

1、极简背诵定义

Exchanger 是 JUC 线程数据交换工具类,仅支持两个线程成对交换数据;线程两两配对、互相阻塞等待,双方抵达交换点后,互换持有数据,交换完成并行执行,属于点对点线程通信工具。

2、底层核心原理(面试必背)

① 底层基于AQS共享模式实现,内部维护交换槽位;

② 线程调用exchange()方法,若无配对线程,当前线程阻塞等待;

③ 第二个线程进入交换逻辑,两者匹配成功,互相交换数据;

④ 交换完成后,两个线程同时唤醒,继续执行业务;

⑤ 仅支持两两交换,不支持多线程批量交换,无存储容量。
3、企业开发实战代码(生产可直接复用)

业务场景:两个线程双向传输业务数据,线程A生成加密数据、线程B生成密钥,互相交换完成数据解密校验。

java 复制代码
import java.util.concurrent.Exchanger;

/**
 * Exchanger 企业实战:双线程数据双向交换
 * 场景:加密数据与密钥配对交换,完成数据校验解密
 */
public class ExchangerBusinessDemo {
    // 创建数据交换器,指定数据交换类型为字符串
    private static final Exchanger<String> EXCHANGER = new Exchanger<>();

    public static void main(String[] args) {
        // 线程A:生成加密报文,等待交换密钥
        new Thread(() -> {
            try {
                String encryptData = "ABC123_加密业务报文";
                System.out.println("线程A:生成加密报文 = " + encryptData);
                // 阻塞等待配对线程,交换数据:传入自身数据,接收对方数据
                String secretKey = EXCHANGER.exchange(encryptData);
                System.out.println("线程A:交换获取密钥 = " + secretKey);
                // 拿到密钥解密业务数据
                System.out.println("线程A:使用密钥解密报文,业务处理完成");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "加密报文线程").start();

        // 线程B:生成密钥,等待交换加密报文
        new Thread(() -> {
            try {
                String key = "MD5_666888秘钥";
                System.out.println("线程B:生成加密密钥 = " + key);
                // 阻塞配对,双向交换数据
                String data = EXCHANGER.exchange(key);
                System.out.println("线程B:交换获取加密报文 = " + data);
                System.out.println("线程B:校验报文合法性,校验完成");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "密钥生成线程").start();
    }
}

4、核心常用方法

exchange(V x) :传入当前线程数据,阻塞等待配对线程,完成数据交换;

exchange(V x, long timeout, TimeUnit unit) :限时交换,超时未配对直接抛出超时异常,避免永久阻塞。
5、真实生产落地场景

双线程数据对接 :报文与密钥双向传输、上下游线程数据校验;

缓冲区交换 :生产者、消费者双缓冲区互换,提升IO读写效率;

简单点对点通信 :无需复杂队列,两个线程临时数据交互;

数据镜像校验 :双线程同步数据,比对数据一致性。
6、高频面试坑点(必考)

严格限制两个线程 ,多线程场景下会随机两两配对,交换混乱;

② 单个线程调用exchange()会无限阻塞,必须等待配对线程;

③ 无数据存储容器,数据实时交换、不持久化;

④ 阻塞可中断,线程中断后直接退出交换阻塞状态;

⑤ 生产极少使用,仅适用于简单双线程点对点交互。
7、一句话总结

Exchanger是双人数据交换工具,两两配对、互相阻塞、双向传值;仅限双线程通信,简单临时交互专用,生产使用频率极低。

5.BlockingQueue 阻塞队列特点?

满队列阻塞入队,空队列阻塞出队。

6.常见阻塞队列?(满分完整版+七大队列+特性+场景+面试必背)

1、标准答案(七大常用阻塞队列)

JUC 常见阻塞队列一共 7 种 :ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue、DelayQueue、LinkedTransferQueue、LinkedBlockingDeque。
2、逐个详细拆解(面试必背)

ArrayBlockingQueue(有界数组阻塞队列)

底层:数组实现、**有界固定容量**;默认非公平锁;

特点:长度固定、不扩容、生产消费速度均衡;

场景:普通限流、固定并发任务缓冲。

LinkedBlockingQueue(无界/有界链表队列)

底层:单向链表、默认无界(最大Integer最大值);

特点:读写分离锁、吞吐量高于数组队列;

坑点:无界不指定容量容易堆积任务引发OOM;

场景:Executors.newFixedThreadPool 默认队列。

SynchronousQueue(同步无容量队列)

底层:无数组无链表、**无存储空间**;

特点:存必须取、一对一配对、不缓存任务;

场景:Executors.newCachedThreadPool 默认队列、瞬时高并发任务。

PriorityBlockingQueue(优先级阻塞队列)

底层:最小堆二叉树、无界队列;

特点:自带排序、自定义优先级、永远优先取出权重最高任务;

场景:优先级任务、订单分级处理、紧急任务插队。

DelayQueue(延迟阻塞队列)

底层:优先级队列+时间排序、无界;

特点:元素实现延时接口,**到期才能取出**,未到期永久阻塞;

场景:订单超时关闭、延时任务、定时重试、会员过期。

LinkedTransferQueue(高效传输队列)

底层:链表、无界;

特点:结合SynchronousQueue+LinkedBlockingQueue优点,可直接传输、可缓存任务,并发性能天花板;

场景:超高并发、海量异步任务传输。

LinkedBlockingDeque(双向阻塞队列)

底层:双向链表、可设置有界;

特点:头尾两端增删、支持双向阻塞;

场景:工作窃取、双端任务读写、定时任务队列。
3、高频面试坑点

① 有界队列:ArrayBlockingQueue、SynchronousQueue、LinkedBlockingDeque(可设置);

② 无界队列:LinkedBlockingQueue(默认)、Priority、Delay、Transfer;

③ 生产禁止使用无界队列,防止任务无限堆积OOM;

④ 所有阻塞队列底层都是 **ReentrantLock+Condition** 实现阻塞唤醒。
4、一句话总结

数组固定、链表无界、同步无容量、优先级排序、延迟到期、双向双端、传输高性能;按需选用、拒绝无界、防止OOM。

7.SynchronousQueue 特点?

无容量,存入必须立即取出。

8.延迟队列 DelayQueue 使用场景?(满分完整版+生产真实案例)

1、核心特性 :无界阻塞队列,底层基于优先级最小堆,元素必须实现Delayed延时接口,**只有到期元素才能被取出**,未到期永久阻塞,无需手动轮询,性能极高。
2、生产高频落地场景(面试必背)

订单超时自动关闭(最经典) :电商下单未支付,15分钟后自动关闭订单、释放库存、回滚优惠券;

会员/权限过期管控 :VIP会员到期自动降级、临时权限到期回收;

延时重试任务 :接口调用失败、消息发送失败,延迟指定时间二次重试;

活动定时启停 :营销活动定时上线、定时下架,无需定时轮询扫描;

任务延时调度 :预约任务、定时提醒、消息延后推送(短信、站内信);

闲置资源回收 :连接池闲置连接超时销毁、临时缓存过期清理。
3、底层优点 :无需while死循环轮询、节约CPU资源、精准延时、无额外定时任务开销。
4、生产痛点坑点

① 无持久化,服务重启内存数据丢失,重要延时任务需搭配数据库持久化;

② 堆结构插入排序,大量任务写入性能一般;

③ 不支持精准时间排序外的复杂延时规则。
5、一句话总结

到期自动取出、未到期阻塞等待;主打订单超时、延时重试、过期回收,轻量无轮询,适合简单延时任务。

9.ConcurrentHashMap1.7 与 1.8 区别?(满分完整版+表格+底层原理+面试必考)

1、一句话极简背诵

JDK1.7分段锁+数组链表、ReentrantLock加锁;JDK1.8数组+链表+红黑树、CAS+synchronized,优化哈希扰动、并发更高、查询更快。
2、七大核心区别(面试必背表格)

对比维度 JDK1.7 JDK1.8
底层数据结构 数组 + 分段链表(Segment 分段锁),双层数组分段存储 数组 + 链表 + 红黑树,取消 Segment 分段,一维数组直接存储
加锁实现方式 ReentrantLock 显式锁,锁住整个 Segment 分段 CAS 无锁插入 + synchronized 内置锁,仅锁住冲突桶位头节点
锁粒度 锁住整个 Segment 分段,一个分段内所有操作互斥 仅锁住当前数组桶位,不同桶位操作互不阻塞,锁粒度极细
哈希扰动算法 4 次位运算扰动,降低哈希碰撞概率 1 次扰动 + 高位参与运算,逻辑更简洁,散列均匀性更优
扩容机制 各 Segment 分段独立扩容,互不影响 全局统一扩容,新增迁移标记节点,支持多线程协助迁移数据
红黑树结构 无红黑树结构,哈希冲突仅靠链表挂载 链表长度 > 8 且数组长度 > 64 转为红黑树;节点数≤6 退化为链表
查询效率 链表遍历查询,大数据量下查询效率低,时间复杂度 O (n) 链表 + 红黑树双结构,长链表查询优化为 O (logN),查询效率大幅提升
并发能力 并发度低,最大仅支持 16 个 Segment 分段并发 并发度极高,桶位独立加锁互不阻塞,理论并发数等于数组长度

3、逐点底层详解

数据结构 :1.7采用Segment数组+HashEntry数组+链表,双层数组分段存储;1.8取消Segment分段,直接采用一维数组,链表过长转化红黑树,平衡查询性能。

加锁方式 :1.7基于ReentrantLock锁住Segment分段,一个分段内数据互斥;1.8摒弃分段锁,采用CAS无锁插入,冲突后加synchronized锁,仅锁定当前桶位节点,锁粒度大幅缩小。

哈希算法优化 :1.7做4次哈希扰动,降低碰撞;1.8简化扰动算法,将哈希值高位挪至低位运算,兼顾性能与散列均匀性。

树化规则 :1.7无红黑树,链表无限变长;1.8规定:链表长度超过8、且数组容量≥64,树化为红黑树;扩容时节点数≤6,红黑树退化为链表,节省空间。

扩容机制 :1.7各Segment独立扩容,互不干扰;1.8全局数组扩容,新增辅助迁移节点,多线程协助迁移数据,提升扩容效率。

锁性能差异 :1.7分段锁最多同时16个线程并发;1.8桶位独立加锁,理论并发数等于数组长度,并发能力大幅提升。
4、高频面试坑点

① JDK1.8放弃ReentrantLock改用synchronized,原因:JDK1.6后锁优化,synchronized性能反超显式锁,更简洁;

② 红黑树转换双重条件:链表长度>8 + 数组长度≥64 ,缺一不树化,防止小容量无效树化;

③ 1.8扩容会保留原数组,新旧数组并存,迁移完成后废弃旧数组;

④ 二者均不允许key/value为null,区别于HashMap。
5、一句话总结

1.7分段锁、链表慢、重入锁;1.8细粒度桶锁、CAS+sync、树化优化、并发性能全面碾压旧版本。

10.ConcurrentHashMap 的工作原理?(满分完整版+底层流程+面试必背)

1、一句话极简背诵

JDK1.8采用数组+链表+红黑树数据结构,依托CAS+synchronized细粒度桶位锁,结合哈希扰动、树化机制、多线程扩容,实现线程安全的高效键值存储,全程无分段锁,并发性能极强。

2、底层核心数据结构

底层主体 :一维哈希数组(Node[] table),数组下标为哈希桶位;

链表结构 :桶位哈希冲突时,尾插法挂载链表节点,存储冲突元素;

红黑树:链表长度>8且数组容量≥64,链表转为红黑树,提升查询效率;节点数≤6,树退化为链表,节约内存。

3、加锁核心原理(重中之重)

① 无冲突:采用CAS无锁自旋 插入节点,不加锁、性能高;

② 发生哈希冲突:对当前桶位头节点加synchronized锁 ,仅锁住单个桶位,不影响其他桶位并发,锁粒度极细;

③ 摒弃JDK1.7分段锁,取消Segment层,简化结构、提升并发度。
4、完整写入执行流程

① 计算key哈希值,通过扰动算法优化哈希,确定数组桶位下标;

② 判断当前桶位是否为空,为空则CAS无锁直接插入节点;

③ 桶位不为空且哈希冲突,锁住桶位头节点,遍历链表/红黑树;

④ key重复则覆盖旧值,无重复则尾插新增节点;

⑤ 插入后判断链表长度,满足树化条件自动转换红黑树。
5、扩容迁移原理

① 数组元素达到扩容阈值(容量*0.75),触发全局扩容,容量翻倍;

② 新增迁移标记节点,支持多线程协助扩容 ,分散迁移压力;

③ 扩容期间新旧数组共存,迁移完成后废弃旧数组,避免数据丢失。

6、关键特性与约束

① 线程安全:CAS+synchronized保证并发读写安全;

② 去重规则:key唯一,重复key覆盖value;

③ 空值约束:key、value均不允许为null ,区别于HashMap;

④ 迭代机制:弱一致性迭代,遍历期间修改不会抛出并发修改异常。
7、高频面试坑点

① synchronized仅锁桶位头节点,不锁数组,并发粒度远超分段锁;

② 树化必须同时满足两个条件:链表长度>8、数组容量≥64;

③ JDK1.8舍弃ReentrantLock,因锁优化后synchronized性能更优;

④ 高并发写入无全局锁,多桶位可同时并行写入。
8、终极总结

数组打底、冲突链表、过长树化;CAS无锁插空、sync锁冲突桶位;多线程扩容、细粒度锁,实现高并发线程安全哈希表。

11.HashMap 的工作原理?线程不安全表现?(满分完整版+底层流程+不安全原因+面试必考)

1、极简背诵定义

HashMap 是 JDK 非线程安全哈希表,JDK1.8 采用数组+链表+红黑树 数据结构,基于哈希算法存储键值对,依靠哈希表快速寻址,不保证线程安全,并发场景会出现数据异常。
2、底层工作原理(JDK1.8 主流版本)

底层结构 :一维哈希数组(默认容量16)+单向链表+红黑树;数组下标为哈希桶位,冲突元素挂载链表,长链表转为红黑树优化查询。

哈希寻址 :通过hash()扰动算法优化key哈希值,高位参与运算,减少哈希碰撞,计算数组下标确定存储桶位。

插入规则 :桶位为空直接插入;桶位冲突采用尾插法 挂载链表;链表长度>8且数组容量≥64,树化为红黑树;节点数≤6退化为链表。

扩容机制 :负载因子默认0.75,元素达到阈值触发扩容,容量翻倍;扩容后重新哈希迁移元素,优化哈希分布。

查询逻辑 :计算哈希定位桶位,桶位首节点直接比对key;链表/红黑树遍历匹配key,返回对应value。
3、HashMap 核心参数

① 默认初始容量:16;② 默认负载因子:0.75;③ 树化阈值:链表长度>8;④ 退化阈值:节点数≤6。
4、多线程下线程不安全四大表现(面试必背)

数据覆盖丢失 :多线程同时put元素,哈希碰撞时未加锁,后插入线程直接覆盖前一个线程数据,造成数据丢失。

扩容死循环(JDK1.7专属) :1.7采用头插法,多线程同时扩容迁移链表,节点引用循环指向,形成环形链表,get查询时CPU死循环飙升。

元素错乱、链表断裂 :JDK1.8尾插法消除死循环,但并发扩容会出现节点丢失、链表断裂、元素顺序错乱问题。

空指针异常、并发修改异常 :并发遍历+修改,底层节点结构突变,抛出异常或读取到null脏数据。
5、不安全底层根本原因

HashMap 无任何加锁机制,并发读写无互斥;扩容、插入、修改节点时,线程互相抢占修改节点引用,导致哈希表结构错乱。
6、面试高频坑点

① JDK1.7头插法有扩容死循环,1.8尾插法解决死循环,但依旧线程不安全

② 负载因子0.75是时间与空间平衡最优值,不建议随意修改;

③ HashMap 允许key/value为null,key仅允许一个null,区别于ConcurrentHashMap;

④ 并发业务禁止使用HashMap,优先选用ConcurrentHashMap。

7、一句话总结

数组打底、冲突链表、过长树化;无锁并发不安全,1.7死循环、1.8数据覆盖,高并发必须换ConcurrentHashMap。

12.ConcurrentHashMap 和 Hashtable 的区别?(满分完整版+表格+底层+面试必考)

1、一句话极简背诵

Hashtable 全局粗暴加锁、并发极差;ConcurrentHashMap 细粒度锁、并发高性能,两者都是线程安全哈希表,生产坚决弃用Hashtable,优先ConcurrentHashMap
2、六大核心区别(面试必背表格)

对比维度 Hashtable ConcurrentHashMap(JDK1.8)
加锁方式 方法上加 synchronized,全局锁,锁整个数组 CAS+synchronized 细粒度桶位锁,仅锁定冲突节点
锁粒度 锁粒度极大,同一时刻仅能一个线程执行读写操作 锁粒度极小,多线程可并行操作不同桶位,互不阻塞
并发性能 并发能力极低,属于老旧淘汰类 并发能力极强,是高并发场景首选方案
空值约束 key、value均不允许为 null key、value均不允许为 null
数据结构 底层仅采用数组 + 单向链表,无红黑树优化 底层采用数组 + 链表 + 红黑树,长链表自动树化优化查询性能
迭代器特性 普通强一致性迭代器,遍历过程中修改元素会直接抛出并发修改异常 弱一致性迭代器,基于快照遍历,遍历期间允许修改元素,不抛异常

3、逐点底层深度拆解

加锁机制不同(核心区别)

Hashtable:在put、get、remove等所有公有方法上加synchronized,锁住整个哈希数组,无论是否冲突,所有线程互斥排队,并发吞吐量极低;

ConcurrentHashMap:无全局锁,空桶位CAS无锁插入,冲突仅锁住当前桶位头节点,不同桶位线程互不阻塞,并发能力拉满。

数据结构不同

Hashtable:底层仅采用数组+单向链表,无红黑树,哈希冲突严重时链表无限变长,查询效率O(n);

ConcurrentHashMap:数组+链表+红黑树,链表过长自动树化,查询效率优化为O(logN)。

迭代机制不同

Hashtable:普通强一致性迭代器,遍历过程中修改元素,直接抛出ConcurrentModificationException并发修改异常;

ConcurrentHashMap:弱一致性迭代器,基于快照遍历,遍历期间允许修改,不抛异常,适合并发读写场景。

初始化与扩容差异

Hashtable:默认初始容量11,扩容方式:原容量*2+1,素数容量减少哈希碰撞;

ConcurrentHashMap:默认初始容量16,扩容容量翻倍,2的幂次,哈希取模运算更高效。
4、高频面试坑点(必考)

① 两者都不允许key/value为null ,区别于HashMap;

② Hashtable是遗留老旧类,底层锁设计笨重,生产环境禁止使用

③ Hashtable全局锁,锁竞争激烈极易线程阻塞;ConcurrentHashMap细粒度锁,适配高并发;

④ JDK1.8之后ConcurrentHashMap优化拉满,全面替代Hashtable成为并发哈希首选。
5、一句话总结

Hashtable全局粗锁、性能拉胯老旧淘汰;ConcurrentHashMap细粒度桶锁、树化优化、高并发高性能,并发场景唯一首选。

13.ConcurrentHashMap 如何进行扩容?(满分完整版+流程+多线程迁移+面试必考)

1、极简背诵定义

JDK1.8 ConcurrentHashMap 满足扩容阈值触发全局翻倍扩容,采用新旧数组共存+多线程协助迁移机制,CAS+synchronized保障迁移安全,扩容期间不阻塞读写,大幅提升扩容效率,无分段独立扩容。

2、扩容触发条件(面试必背)

① 底层哈希数组元素数量 ≥ 容量 * 负载因子(0.75) ,触发正常扩容;

② 链表树化触发扩容:链表长度>8,但数组容量<64,不满足树化条件,优先扩容;

③ 初始容量最小为16,扩容容量严格遵循2倍翻倍 ,始终保持2的幂次。
3、核心扩容底层机制

容量翻倍 :原数组容量左移一位(*2),新建空的扩容数组;

迁移标记位 :定义特殊ForwardingNode迁移节点,标记当前桶位正在迁移,防止重复迁移、并发修改;

多线程协助迁移 :主线程迁移过程中,其他写入/查询线程发现扩容标记,主动协助搬运数据,分散迁移压力;

高低位拆分迁移 :原桶位数据无需重新哈希计算,依据哈希值高位划分:高位为0留原下标,高位为1迁移至【原下标+旧容量】新下标,迁移算法极简高效。
4、完整扩容迁移流程

① 判断元素数量达到扩容阈值,初始化二倍容量的新数组;

② 遍历原数组桶位,为空桶位直接标记跳过,无需迁移;

③ 有数据桶位加synchronized锁,拆分链表/红黑树为高低位两条链表;

④ 分别迁移至新数组对应下标,原桶位标记为迁移节点;

⑤ 全部桶位迁移完成,废弃旧数组,新数组替代原数组,扩容结束。
5、扩容线程安全保障

① 单个桶位加锁,不同桶位可并行迁移;

② ForwardingNode标记防止重复迁移,规避并发异常;

③ 迁移期间读写线程互不阻塞,读操作优先读取新旧数组有效数据。
6、高频面试坑点(必考)

① JDK1.7分段独立扩容,1.8改为全局统一扩容

② 扩容无需重算哈希,依靠高位拆分,节省算力;

③ 多线程协助扩容是核心优化,高并发下大幅缩短扩容耗时;

④ 扩容期间新旧数组共存,不会丢失数据,保证数据一致性。
7、一句话总结

达到阈值二倍扩容、高位拆分免重哈希、多线程协助搬运、桶位加锁保安全;迁移标记防重复,扩容高效且线程安全。

14.为什么 ConcurrentHashMap 不允许使用 null 作为键或值?(满分完整版+底层原因+面试必考)

1、一句话极简背诵

核心原因:并发环境下无法区分 null 是键不存在,还是存入的 null 值 ,为避免歧义、防止空指针隐患、适配并发查询,直接禁止null键和null值;而HashMap是非并发容器,无此顾虑。
2、两大核心底层原因(面试必背)

并发查询歧义问题(最根本原因)

ConcurrentHashMap 支持并发读写,多线程环境下,调用get(key)返回null时,无法判定两种情况:

✅ 情况1:该key本身不存在;

✅ 情况2:该key真实存在,存入的value就是null。

非并发的HashMap可以通过containsKey(key)二次判断区分;但并发场景下,两次查询之间数据可能被其他线程修改 ,判定结果失效,存在严重逻辑歧义。

空指针安全约束+设计规范

JUC并发容器设计初衷就是规避空指针异常、减少线上隐患;null代表空占位,无实际业务含义,并发存取null极易引发空指针、逻辑判断异常;因此JUC所有并发Map容器,统一禁止key、value为null。
3、HashMap 允许null的对比区别

① HashMap:单线程环境,可通过containsKey二次校验,无并发数据篡改风险,允许一个null键、多个null值;

② ConcurrentHashMap:并发多线程,二次校验失效,无法区分null语义,直接全部禁止。
4、高频面试坑点

① 不要只记空指针,核心考点是并发歧义无法判定

② Hashtable同样禁止null键值,底层逻辑和ConcurrentHashMap一致;

③ 业务中存入空数据,建议自定义空标识常量,禁止用null占位。
5、一句话总结

单线程HashMap可校验区分null,并发ConcurrentHashMap无法区分null语义;为杜绝并发歧义、规避空指针,直接禁止null键和null值。

15.CopyOnWriteArrayList 原理?(满分完整版+底层+代码+面试必考)

1、一句话极简背诵

核心原理:写时复制、读写分离、读无锁、写加锁 ;修改元素时拷贝出新数组,在新数组完成写入,修改完毕替换原数组,读操作全程无锁,极致适配读多写少并发场景。
2、底层核心原理(面试必背)

底层数据结构 :内部维护volatile 修饰的Object数组 ,volatile保证数组引用实时可见,禁止指令重排;

写时复制机制 :查询、遍历等读操作直接读取原数组,不加任何锁;新增、删除、修改等写操作,先加ReentrantLock显式锁,防止并发写入冲突,再拷贝原数组生成长度+1/同等长度的新数组,在新数组完成数据修改,最后用新数组覆盖原数组引用;

读写分离 :读旧数组、写新数组,读写互不阻塞,读操作性能极高;

迭代器弱一致性 :迭代时基于快照遍历,遍历期间修改数组不会抛出并发修改异常。
3、底层执行流程(以add新增为例)

① 写入前加ReentrantLock锁,保证单线程写入;

② 获取原数组,复制原数组元素,生成新数组;

③ 在新数组末尾添加元素;

④ 将volatile数组引用指向新数组;

⑤ 释放锁,写入完成。
4、极简源码核心片段 // 底层volatile数组,保证引用可见性

java 复制代码
private transient volatile Object[] array;

// add方法核心逻辑
public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock(); // 写操作加锁
    try {
        Object[] elements = getArray();
        int len = elements.length;
        // 拷贝生成新数组
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        // 替换原数组引用
        setArray(newElements);
        return true;
    } finally {
        lock.unlock(); // 释放锁
    }
}

5、核心优缺点(衔接下一题,逻辑连贯)

① 优点:读无锁、查询极速;并发读不阻塞、读写不阻塞;迭代不抛并发修改异常;

② 缺点:写入频繁会频繁拷贝数组,占用大量内存、产生GC;数据存在弱一致性、实时性差 ,读取不到最新写入数据;不适合大批量写操作。
6、高频面试坑点

① 加锁方式:写操作使用ReentrantLock显式锁 ,读操作无锁;

② 数组是volatile修饰,仅保证引用可见,不保证数组内部元素可见;

③ 每次写入必拷贝数组,数组越大拷贝开销越高;

④ 允许元素为null,支持重复元素,有序可重复。
7、生产适用场景

专门适配读多写少 场景:白名单、配置列表、权限菜单、静态常量集合、极少修改的缓存列表;禁止用于高频写入业务。
8、一句话总结

写加锁、读无锁,修改先拷贝、改完换引用;读多写少专用,牺牲内存换并发读性能,存在数据弱一致性。

16.CopyOnWriteArrayList 缺点?

占用内存、数据实时性弱。

17.ThreadLocal 的工作原理?作用?(满分完整版+底层+代码+面试必考)

1、一句话极简背诵

ThreadLocal 是线程本地存储工具 ,为每个线程单独开辟私有存储空间,线程之间数据严格隔离、互不共享;底层线程自带Map存储私有数据,无锁实现线程安全,专门解决线程变量隔离问题。
2、核心作用(面试必背)

线程数据隔离 :每个线程拥有独立变量副本,线程之间互不干扰、互不覆盖;

无锁并发安全 :不依赖任何锁,规避并发竞争,性能极高;

上下文数据传递 :同一线程内跨方法、跨层级透明传参,简化代码;

保存线程私有上下文 :存储用户登录信息、请求链路ID、数据库连接等线程专属数据。
3、底层工作原理(通俗易懂)

存储结构 :每个 Thread 线程内部持有 ThreadLocalMap 集合;

存储关系 :以 ThreadLocal 对象为 key,存储的业务数据为 value;

存取逻辑 :调用set()时,获取当前线程的ThreadLocalMap,存入键值对;调用get()时,从当前线程Map中取值;

隔离本质 :数据存在线程自身内部,而非ThreadLocal,天生线程隔离,无需加锁。
4、标准实操代码(业务通用模板)

java 复制代码
// 定义线程本地变量,存储用户信息
private static final ThreadLocal<UserInfo> USER_LOCAL = new ThreadLocal<>();

// 存入当前线程上下文(拦截器解析登录用户)
USER_LOCAL.set(userInfo);
// 同一线程任意位置获取用户信息
UserInfo info = USER_LOCAL.get();
// 使用完毕必须移除,防止内存泄漏
USER_LOCAL.remove();

5、底层核心特性

① 数据绑定当前线程,生命周期跟随线程;

② 底层Entry的key为弱引用 ,防止ThreadLocal对象内存常驻;

③ 不支持父子线程数据传递,子线程无法获取父线程本地数据;

④ 单线程内可存储多个不同类型本地变量。
6、生产高频使用场景

① Web请求上下文:存储登录用户、Token、请求IP,全局透明获取;

② 数据库会话:单线程绑定同一个数据库连接,避免频繁创建销毁;

③ 日志链路追踪:存放traceId,串联同一线程全部日志;

④ 简单工具隔离:非线程安全工具类(SimpleDateFormat),线程私有实例隔离。
7、高频面试坑点(必考)

① ThreadLocal 不共享数据 ,仅作用于当前线程;

② 线程池环境必须手动remove,线程复用会产生脏数据;

③ 弱引用key回收后,value强引用残留,引发内存泄漏;

④ 禁止存储大对象,长期常驻线程容易堆积内存。

8、一句话总结

线程自带本地Map、key弱引用;实现线程数据隔离、无锁安全、上下文传参;用完务必remove,严防内存泄漏与线程复用脏数据。

18.ThreadLocal 底层结构?(满分完整版+源码+哈希规则+面试必考)

1、整体层级结构(面试必背)

层级链路:Thread 线程 → ThreadLocalMap 内部集合 → Entry 存储节点 ;数据不存在ThreadLocal中,而是存放在当前线程内部的ThreadLocalMap里,线程私有、天然隔离。
2、三大核心组成部分

Thread(线程) :每个Thread内部维护一个ThreadLocalMap成员变量,初始为null,首次set才创建;

ThreadLocalMap(内部哈希表) :ThreadLocal静态内部类,自定义简易哈希表,无链表、无红黑树;底层数组存储Entry,默认容量16,负载因子2/3;

Entry(存储节点) :ThreadLocalMap静态内部类,专门存储键值对;key为ThreadLocal弱引用、value为业务强引用
3、Entry底层源码结构 // 底层Entry核心源码

java 复制代码
static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;
    // key弱引用、value强引用
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

4、哈希寻址规则(区别HashMap)

① 哈希算法:ThreadLocal采用固定魔数0x61c88647 黄金哈希值,均匀散列下标,减少哈希冲突;

② 冲突解决:无链表、无树化 ,采用线性探测法,哈希冲突向后顺延找空槽位;

③ 扩容机制:数组元素数量达到容量2/3触发扩容,容量翻倍,重新哈希迁移元素。
5、底层核心特点

① 专属私有:一个线程仅一个ThreadLocalMap,多线程互不干扰;

② 弱引用key:防止ThreadLocal对象被回收后,key常驻内存;

③ 自动清理:遍历数组时,发现key过期的无效Entry,自动清空value、释放内存;

④ 结构极简:舍弃复杂链表红黑树,线性探测适配少量线程本地变量存储。
6、高频面试坑点

① 不要混淆存储位置:数据存在Thread里,不是ThreadLocal

② 线性探测冲突效率低,不适合存储海量本地变量;

③ key弱引用+value强引用组合,是内存泄漏的根本结构原因;

④ Map延迟创建,线程无本地变量时不占用内存。
7、一句话总结

线程内置Map、数组+Entry节点、key弱引用;黄金哈希散列、线性探测解决冲突,结构轻量专为线程私有数据设计。

19.ThreadLocal 内存泄漏原因?(满分完整版+底层结构+泄漏流程+面试必考)

1、一句话极简背诵

底层key弱引用、value强引用 ;ThreadLocal被回收后key清空,value无法自动释放,长期驻留线程造成内存泄漏,线程池复用场景泄漏最严重。
2、底层根本原因(核心必考)

Entry存储结构缺陷 :ThreadLocalMap的Entry节点,key(ThreadLocal)为弱引用 ,value(业务数据)为强引用

GC回收机制差异 :发生GC时,无强引用的key会被弱引用机制自动回收置为null;但value是强引用,当前线程只要不销毁,value就一直被线程引用,无法被GC回收;

数据残留堆积:key为null、value存活,形成无效脏数据,长期堆积在ThreadLocalMap中,造成内存泄漏。

3、完整泄漏执行流程

① 业务代码中ThreadLocal引用断开(局部变量结束生命周期);

② GC触发,弱引用key被回收,Entry中key变为null;

③ 线程持续存活(线程池/常驻核心线程),线程内部ThreadLocalMap不会销毁;

④ 强引用value一直绑定线程,无法被垃圾回收,永久残留内存,产生内存泄漏。
4、两大高发泄漏场景

线程池环境(最严重) :线程复用不会销毁,旧线程残留的null-key+value脏数据永久堆积;

常驻后台线程 :JVM守护线程、定时任务线程长期存活,无法自动清空残留数据。
5、补充面试坑点

① 若线程直接销毁,线程内部Map全部回收,不会产生内存泄漏

② 弱引用不是漏洞,是设计者为防止ThreadLocal对象内存常驻;泄漏根源是强弱引用搭配缺陷+不手动清理

③ 存储大对象、集合类数据,泄漏造成的内存占用会急剧放大。
6、一句话总结

key弱引用被GC回收、value强引用滞留线程;线程不销毁、数据不释放,强弱引用结构缺陷引发内存泄漏。

20.如何解决 ThreadLocal 内存泄漏?(满分完整版+规范代码+生产强制规范)

1、核心最优解决方案(面试必背)

使用完毕,必须在finally代码块手动调用remove()清除数据 ,主动断开强引用value,彻底释放内存,从根源杜绝内存泄漏。
2、四大落地解决手段(生产强制执行)

手动remove清除(最根本、必做)

业务逻辑执行完毕,无论是否异常,在finally中强制删除当前线程本地数据,清空Entry的key和value,断开强引用链路,防止数据残留。

尽量使用try-finally标准模板

严格规范编码格式,set存入数据后,finally执行remove,保证任何情况下都能清除,杜绝遗漏。

线程池环境重点管控

线程池线程长期复用、不会销毁,用完必须remove;否则复用线程会读取上一次残留脏数据,同时堆积内存泄漏。

避免存储大对象、长生命周期数据

ThreadLocal适合存储轻量上下文数据,禁止存放集合、大实体、长缓存对象,减少泄漏内存占用体量。
3、生产标准安全代码模板(直接复用)

java 复制代码
// 定义线程本地变量
private static final ThreadLocal<UserInfo> USER_LOCAL = new ThreadLocal<>();

public void bizMethod(){
    try {
        // 存入线程上下文
        USER_LOCAL.set(new UserInfo());
        // 执行业务逻辑
        doBusiness();
    } finally {
        // 【强制】用完手动移除,根治内存泄漏
        USER_LOCAL.remove();
    }
}

4、辅助底层优化(JDK自带)

ThreadLocalMap底层自带**被动清理机制**:扩容、重新哈希、遍历查询时,自动扫描key为null的无效Entry,清空value释放内存;但该机制触发不可控、被动滞后,不能作为依赖,只能辅助兜底。

5、高频面试坑点

① 仅依靠JDK自动清理无法根治泄漏 ,触发时机随机,生产必须手动remove;

② 线程池、定时任务常驻线程,是泄漏重灾区,编码零容忍遗漏;

③ 禁止将ThreadLocal定义为静态常量后不清理,长期堆积脏数据;

④ 弱引用是兜底保护,不是解决方案,切勿混淆概念。
6、一句话终极总结

finally手动remove是唯一根治手段,try-finally规范编码、线程池严格清理、杜绝大对象存储;被动清理仅兜底,手动清除才是王道。

21.InheritableThreadLocal 作用?(满分完整版+底层原理+坑点)

1、一句话极简背诵

InheritableThreadLocal 是可继承的线程本地变量 ,支持**父线程向子线程自动传递数据**,创建子线程时拷贝父线程本地数据,实现父子线程数据共享。
2、核心底层原理

① 继承ThreadLocal,重写childValue()拷贝方法;

② 创建子线程时,JVM主动检测父线程的InheritableThreadLocalMap;

③ 浅拷贝父线程数据至子线程独立Map,拷贝后父子线程数据相互隔离,互不影响;

④ 普通ThreadLocal子线程无法获取父线程数据,该类专门解决父子线程传参问题。
3、适用业务场景

① 主线程开启子线程,同步传递登录用户、请求上下文;

② 简单父子线程链路数据透传,无需手动赋值。
4、高频致命坑点(面试必考)

不支持线程池 :线程池线程复用,只会在线程创建时拷贝一次数据,复用不会刷新,产生脏数据;

② 数据为浅拷贝,引用类型数据修改会互相影响;

③ 子线程创建开销大,大量创建子线程性能损耗高。
5、一句话总结

原生支持父子线程数据传递、创建子线程浅拷贝数据;简单异步子线程适用,线程池环境直接失效。

22.TransmittableThreadLocal 作用?(满分完整版+底层+线程池痛点+生产场景)

1、一句话极简背诵

阿里开源工具类,专门解决线程池环境下父子线程上下文传递失效 问题;原生InheritableThreadLocal线程复用脏数据,TransmittableThreadLocal实现线程池完美上下文透传。
2、核心底层作用

① 继承InheritableThreadLocal,保留父子线程数据拷贝能力;

② 修饰线程池、捕获任务提交时刻上下文快照,每次提交任务都刷新上下文

③ 任务执行完毕自动清除上下文,杜绝线程复用产生脏数据;

④ 无侵入式透传,适配Spring、Dubbo、MDC日志链路追踪。
3、解决行业痛点(必考)

原生缺陷:InheritableThreadLocal 仅线程创建时拷贝一次数据 ,线程池线程长期复用,旧上下文残留,出现登录用户错乱、traceId混乱;

优化方案:TransmittableThreadLocal 在任务提交时复制当前主线程上下文,任务执行前赋值给工作线程,执行结束清空,完美适配线程池复用场景。
4、生产落地场景

① Web异步线程、线程池异步任务透传登录用户信息;

② MDC日志链路追踪,异步线程保持同一个traceId;

③ Dubbo异步调用、网关链路上下文透传;

④ 分布式服务之间请求头、Token透传。

5、高频面试坑点

① JDK原生无此类,依赖阿里ttl依赖包

② 必须搭配修饰线程池使用,否则无法生效;

③ 核心原理:任务提交快照+执行赋值+结束清空。
6、一句话总结

专治线程池上下文传递,弥补InheritableThreadLocal复用脏数据缺陷;异步线程、日志追踪、用户透传生产必备。

23.ScopedValue 作用?(JDK21新特性+满分完整版+对比ThreadLocal+面试必背)

1、一句话极简背诵

ScopedValue 是 JDK21 推出的作用域线程本地变量 ,专为替代 ThreadLocal 而生;作用域可控、自动销毁、无内存泄漏、不可修改,极简实现线程上下文透传,是虚拟线程配套核心工具。
2、核心官方定义

ScopedValue 属于不可变、作用域受限的线程绑定变量,一旦绑定赋值,生命周期固定在指定代码作用域内,代码执行完毕自动释放资源,无需手动remove,从底层根治ThreadLocal内存泄漏痛点。
3、四大核心特性(面试必背)

作用域可控 :绑定代码块作用域,出作用域立即失效,生命周期清晰;

天然无泄漏 :无需手动移除,JVM自动回收资源,彻底解决ThreadLocal内存残留问题;

不可变安全 :赋值后不可修改,杜绝并发篡改、脏数据问题;

适配虚拟线程 :专为JDK21虚拟线程优化,轻量化、低开销,适配海量IO密集型异步任务。
4、和 ThreadLocal 核心区别(面试加分)

对比维度 ThreadLocal ScopedValue
可变特性 支持二次 set 修改 赋值后不可修改、天然安全
生命周期 跟随线程存活 绑定代码作用域,出域自动销毁
泄漏风险 弱引用结构缺陷,极易引发内存泄漏 JVM 自动回收资源,零内存泄漏风险
适配线程 仅适配普通物理线程 完美适配虚拟线程 + 物理线程
使用复杂度 需在 finally 中手动 remove 移除,编码繁琐 简单优雅,无需手动清理,自动释放资源

5、极简代码演示

java 复制代码
// 定义作用域变量
private static final ScopedValue<UserInfo> USER_SCOPE = ScopedValue.newInstance();

// 绑定作用域,执行业务,结束自动释放
ScopedValue.where(USER_SCOPE, new UserInfo()).run(() -> {
    // 当前作用域内任意位置获取数据
    UserInfo info = USER_SCOPE.get();
    // 执行业务逻辑
});
// 出作用域,数据自动失效,无需手动删除

6、生产适用场景

① Web请求上下文透传用户、Token、链路ID;

② 虚拟线程异步任务上下文传递;

③ 禁止修改、要求只读的线程私有上下文数据;

④ 规避内存泄漏的常驻线程、线程池场景。
7、高频面试坑点

① JDK21及以上版本专属,低版本无法使用;

② 赋值后不可二次修改 ,适合只读上下文;

③ 不支持无限存储,严格绑定代码作用域,灵活性低于ThreadLocal;

④ 是官方主推的ThreadLocal终极替代方案。
8、一句话总结

JDK21全新作用域变量,只读不可改、出域自动销毁、零内存泄漏;适配虚拟线程,官方强力替代老旧ThreadLocal。

五、线程池核心(81-90 题)

1.为什么使用线程池?

复用线程、减少创建销毁开销、控制并发、统一管理。

2.线程池七大核心参数?(满分完整版+通俗解释+面试必背)

1、标准答案(七大参数全背诵)

ThreadPoolExecutor 线程池七大核心参数:核心线程数、最大线程数、空闲存活时间、时间单位、阻塞队列、线程工厂、拒绝策略

2、逐个参数详细拆解(面试必考)

corePoolSize(核心线程数):线程池常驻存活线程,线程池初始化后不销毁(默认),即使空闲也会保留,用来随时处理任务;

maximumPoolSize(最大线程数):线程池能创建的线程总数上限,任务暴增、队列塞满后,才会新建非核心线程;

keepAliveTime(空闲存活时间):非核心线程闲置超时时间,空闲超过该时间,非核心线程自动销毁,节省资源;

TimeUnit(时间单位):配合空闲时间使用,毫秒、秒、分钟等时间计量单位;

BlockingQueue(阻塞队列):任务缓冲队列,核心线程满负荷后,新增任务存入队列排队,常用队列:Array、Linked、SynchronousQueue;

ThreadFactory(线程工厂):专门用来创建线程,可自定义线程名称、线程优先级、是否守护线程,方便线上日志排查;

RejectedExecutionHandler(拒绝策略):核心线程+队列+最大线程全部打满,线程池饱和,触发拒绝策略处理超额任务。

3、通俗流程串联(背诵口诀)

常驻核心线程干活 → 任务多了队列排队 → 队列满了新建临时工(非核心线程) → 达到最大线程数触发拒绝策略 → 临时工空闲超时自动销毁。

4、高频面试坑点

① 核心线程默认不会被回收,开启allowCoreThreadTimeOut参数后,核心线程也可超时回收; ② 最大线程数 = 核心线程数 + 非核心线程数;

③ 业务必须自定义线程工厂,给线程命名,便于线上异常排查;

④ 拒绝策略禁止默认抛异常,生产需自定义降级策略。

5、一句话总结核心常驻、最大封顶、空闲超时、队列缓冲、工厂造线程、饱和拒任务,七大参数管控线程池全生命周期。

3.线程池执行流程?(满分完整版+通俗流程+面试必背)

1、极简背诵流程(面试直接默写) 接收任务 → 判断核心线程数 → 核心线程未满新建核心线程;核心线程已满存入阻塞队列 → 队列已满新建非核心线程 → 达到最大线程数触发拒绝策略。

2、五步详细执行流程(底层完整版)

判断核心线程数:提交任务,线程池检测当前运行线程数是否小于核心线程数;若是,直接新建核心线程执行任务;

判断阻塞队列:当前线程数≥核心线程数,线程池判断阻塞队列是否已满;队列未满,将任务存入队列排队等待执行;

判断最大线程数 :队列已经存满,无法继续缓存任务;判断当前总线程数是否小于最大线程数;若是,新建非核心临时工线程处理任务;

触发拒绝策略:总线程数达到最大线程数、队列也塞满,线程池彻底饱和;触发预设拒绝策略,处理超额任务;

线程回收销毁:任务执行完毕后,非核心线程空闲时间超过设定存活时间,自动销毁释放资源;核心线程默认常驻不销毁。

3、通俗口诀(快速记忆)

先开核心干活、再进队列排队、队列满了扩招、满人直接拒绝、临时工超时下岗。

4、高频面试坑点

线程池不会预先创建核心线程,默认懒加载,提交任务才创建;

② 永远优先用队列缓冲,不会直接新建大量非核心线程,避免频繁创建线程消耗性能;

③ 只有队列满了,才会扩容创建非核心线程;

④ 核心线程可手动开启超时回收,和非核心线程一起销毁。

5、一句话总结先核心、再队列、后扩容、满拒绝;临时工超时销毁,核心线程常驻待命。

4.四种拒绝策略?(满分完整版+源码+场景+面试必背)

1、一句话极简背诵 线程池饱和(核心线程+队列+最大线程全部占满)触发拒绝策略;四种策略:抛异常、主线程执行、丢弃队首、直接丢弃。

2、四大拒绝策略详细拆解(面试必背)

Java内置四种拒绝策略,全部实现 RejectedExecutionHandler 接口:

AbortPolicy(默认策略、直接抛异常)

原理:任务饱和直接抛出 RejectedExecutionException 运行时异常,中断业务;

特点:直白报错、方便排查问题;

缺点:生产无处理会直接接口报错、服务异常;

适用:测试环境、需要强提醒报错、不可丢失任务场景。

CallerRunsPolicy(调用者主线程执行)

原理:不抛异常、不丢弃任务;把超额任务退回给提交任务的主线程执行;

特点:主线程同步干活,减缓提交速度,天然限流;

缺点:阻塞主线程,影响接口响应速度;

适用:不允许丢任务、流量平缓、对响应耗时不敏感场景。

DiscardOldestPolicy(丢弃队首最老任务)

原理:丢弃阻塞队列中**最先排队、最老旧**的未执行任务,腾出位置放入新任务;

特点:牺牲旧任务、保留新任务;

缺点:无日志提示,静默丢失任务,数据无告警;

适用:时效性低、老旧任务无意义、实时性高的业务。

DiscardPolicy(直接丢弃当前任务)

原理:静默丢弃当前超额新任务,**无异常、无日志、无提示**;

特点:极致安静、不报错不阻塞;

缺点:任务丢失无感知,线上隐患极大;

适用:可丢弃、非核心、无关紧要的流量任务。

3、策略对比汇总表格

拒绝策略 核心处理方式 是否抛异常 是否阻塞主线程 是否丢失任务 核心适用场景
AbortPolicy(默认策略) 任务饱和直接抛出RejectedExecutionException运行时异常,中断业务执行 不阻塞 不丢失 测试环境、需要强报错校验、不可丢失核心任务的场景
CallerRunsPolicy 不抛异常、不丢弃任务,将超额任务退回给提交任务的主线程同步执行 阻塞 不丢失 流量削峰限流、不可丢失任务、对响应耗时不敏感的业务场景
DiscardOldestPolicy 丢弃阻塞队列中最先排队、最老旧的未执行任务,腾出队列位置放入新任务 不阻塞 丢弃老旧任务 时效性要求高、老旧任务无业务价值、优先保障新任务执行的场景
DiscardPolicy 静默丢弃当前超额新任务,无异常、无日志、无任何提示 不阻塞 丢弃当前新任务 非核心、可舍弃、无业务影响的低优先级流量任务

4、生产开发规范(高频面试坑点)

① 线程池默认策略AbortPolicy,线上大量流量会批量抛异常,禁止直接使用;

② 生产绝对不推荐 Discard 系列,静默丢任务无日志,排查灾难;

③ 流量削峰、可控业务优先 CallerRunsPolicy 天然限流;

④ 高并发秒杀、时效性业务可用 DiscardOldestPolicy;

⑤ 大厂生产一般自定义拒绝策略:打印日志、存入MQ、降级兜底、告警通知。

5、一句话总结默认抛异常、回退主线程、丢弃老任务、丢弃新任务;线上禁用默认抛异常+静默丢弃,优先自定义降级拒绝策略。

5.禁止使用 Executors 创建线程池原因?(满分完整版+源码风险+生产大坑)

1、一句话极简背诵

Executors 创建的线程池存在无界队列、最大线程数无上限问题,高并发下极易引发 OOM 内存溢出,且参数不透明、不可定制,生产强制禁用。

2、四种常用线程池底层致命风险(面试必背)

newFixedThreadPool(固定线程池)

底层:使用无界阻塞队列 LinkedBlockingQueue

风险:任务无限堆积、队列无上限,大量请求积压持续占用内存,直接 OOM。

newSingleThreadExecutor(单线程池)

底层:同样使用无界 LinkedBlockingQueue;

风险:单线程串行执行,任务无限积压,内存持续暴涨引发 OOM。

newCachedThreadPool(缓存线程池)

底层:最大线程数为 Integer.MAX_VALUE,无上限;

风险:无限创建线程,线程数量过多耗尽操作系统资源,导致线程溢出、服务卡死。

newScheduledThreadPool(定时线程池)

底层:核心线程固定,非核心线程无上限;

风险:大量定时任务触发,无限创建线程,造成内存溢出、CPU 飙升。

3、三大核心禁用原因(面试满分总结)

队列/线程无边界:无界队列无限存任务、部分线程池线程无上限,不可控;

风险不可预判:流量突增瞬间堆积任务/线程,线上毫无缓冲直接 OOM、服务宕机;

参数不灵活:Executors 封装死参数,无法自定义拒绝策略、线程名称、空闲时间,生产无法适配复杂业务。

4、生产开发规范(强制编码规约)

✅ 必须手动通过 ThreadPoolExecutor 原生构造方法创建线程池;

✅ 自定义七大核心参数:有界队列、合理线程数、自定义拒绝策略、业务线程名称;

✅ 杜绝无界队列、杜绝线程无上限,从源头规避内存溢出风险。

5、一句话总结Executors 封装无脑、无界队列堆任务、无限线程耗资源;极易OOM、生产高危,业务必须手动原生构建线程池。

6.IO 密集与 CPU 密集线程池配置?(满分完整版+计算公式+生产最优配置)

1、核心判定标准

CPU密集型:大量计算、逻辑运算、循环判断、加密解密、序列化,CPU一直处于高速运算状态,极少阻塞;

IO密集型:数据库查询、Redis缓存、网络请求、文件读写、接口调用,线程大量时间阻塞等待IO响应,CPU空闲。

2、面试必背计算公式

CPU密集型:核心线程数 = CPU核心数 + 1

IO密集型:核心线程数 = CPU核心数 * 2 ~ 5

通用优化公式(生产大厂):核心线程数 = CPU核心数 / (1 - 阻塞系数),阻塞系数0~1,IO越多阻塞系数越大。

3、详细配置原理

CPU密集配置原理:CPU核心有限,线程过多会造成频繁上下文切换,降低运算效率;仅多开1条线程兜底,防止CPU核心空闲,最大化压榨计算性能;

IO密集配置原理:线程大量时间阻塞等待IO,CPU空闲,可多创建线程抢占CPU资源,提升系统吞吐量,不浪费CPU算力。

4、生产环境最佳实践

CPU密集:核心线程数=本机CPU核心数+1、有界队列偏小、拒绝策略降级,禁止大量线程抢占CPU;适合大数据运算、批量解析、复杂算法业务。

IO密集:核心线程数放大2~5倍、队列适中,容忍大量阻塞线程;适合接口请求、数据库操作、文件、缓存交互业务。

5、高频面试坑点

① 不要盲目设置超大线程数,CPU密集线程过多=频繁上下文切换=性能暴跌;

② IO密集无固定线程数,阻塞时间越长,可配置线程数越大;

③ 线上服务器建议通过代码获取CPU核心数:Runtime.getRuntime().availableProcessors(),不要硬编码;

④ 混合业务(既有计算又有IO),折中配置,压测调优为准。

6、一句话总结CPU密集少线程、防切换;IO密集多线程、填空闲;公式为参考、压测定最终。

7.核心线程会被回收吗?

默认不会,allowCoreThreadTimeOut 开启可回收。

8.线程池如何优雅关闭?

shutdown () 平稳关闭;shutdownNow () 强制关闭。

9.ScheduledThreadPool 定时线程池原理?

延迟队列实现定时任务。

10.虚拟线程 Virtual Thread 优点?(JDK21+满分完整版+底层原理+面试必考)

1、一句话极简背诵 虚拟线程是JDK21推出的轻量级用户态线程,不绑定OS内核线程,开销极低、无需手动池化、海量创建无压力,专治IO密集型阻塞浪费,是Java并发革命性升级。

2、五大核心优点(面试必背)

极致轻量化、占用资源极低 :虚拟线程不独占操作系统内核线程,栈内存极小(几百字节);传统物理线程栈内存MB级别,一台服务器仅能创建几千条,虚拟线程可轻松创建百万级、千万级,无线程数量上限瓶颈。

阻塞不浪费载体线程 :物理线程阻塞时会一直占用内核线程资源;虚拟线程遇到IO、sleep、锁阻塞时,自动卸载载体线程,载体线程去执行其他任务,彻底消除阻塞空转浪费,CPU利用率拉满。

无需手动使用线程池:原生无需池化复用,创建销毁成本近乎忽略不计;业务直接新建虚拟线程,告别线程池参数调优、线程复用脏数据问题,编码极简。

兼容性极强、无侵入改造:完美兼容原有Thread API,语法无改动;原有多线程代码无需重构,仅切换线程实现即可使用虚拟线程,适配所有同步代码、锁工具。

配套新特性、生态完善:搭配ScopedValue替代ThreadLocal、StructuredTaskScope结构化并发,自动管控线程生命周期,自动回收资源,杜绝内存泄漏、线程泄露。

3、底层核心原理 虚拟线程属于用户态线程,由JVM自主调度,而非操作系统调度;采用载体线程(平台线程)挂载执行,多虚拟线程复用少量载体线程,阻塞时动态解绑,实现资源高效复用。

4、适用业务场景

✅ 主打IO密集型任务:HTTP接口调用、数据库、Redis、文件IO、网络请求;

✅ 大量短时异步任务、批量查询、第三方接口批量调用;

✅ 简单异步业务,规避线程池复杂参数配置。

5、高频面试坑点(必考)

① 虚拟线程不适合CPU密集型:大量计算场景下,调度切换开销会拉低性能,物理线程更优;

② JDK21及以上长期支持版本可用,低版本无适配;

③ 虚拟线程默认非守护线程,虚拟机不会自动退出;

④ 禁用线程池池化虚拟线程,本身创建销毁无成本,池化无意义。

6、一句话总结轻量无栈开销、阻塞不浪费线程、海量创建无压力、无需线程池;专攻IO密集,JDK21并发官方终极优化。

六、死锁 + 高并发 + 进阶(91-100 题)

1.死锁四大必要条件?(满分完整版+通俗解释+面试必背)

1、一句话极简背诵 死锁产生必须同时满足互斥、请求保持、不可剥夺、循环等待四大条件,缺一不可;破坏任意一个条件即可避免死锁。

2、四大条件详细拆解(面试必考)

互斥条件:同一资源同一时刻只能被一个线程占用,不可共享;锁资源具有排他性,一个线程持有锁,其他线程只能阻塞等待。

请求且保持条件:线程已经持有一把锁,在不释放当前锁的前提下,继续请求获取其他新锁,持有旧锁、申请新锁。

不可剥夺条件:线程获取锁之后,其他线程无法强制抢夺、强行回收该锁;只能由持有锁的线程主动执行完毕后手动释放。

循环等待条件:多个线程之间形成环状锁依赖,线程A等B的锁、线程B等C的锁、线程C等A的锁,无限循环互相等待。

3、通俗举例

线程1持有锁A、请求锁B;线程2持有锁B、请求锁A;互斥占用、互不释放、互相等待,最终形成永久死锁。

4、面试核心口诀互斥占有、请求保持、不可强夺、循环等待。

5、一句话总结四大条件同时满足必死锁,生产打破任意一条,即可杜绝死锁发生。

2.如何避免死锁?(满分完整版+四大方案+生产规范+口诀)

1、极简背诵核心原理

死锁必须同时满足四大条件,生产只要破坏任意一个必要条件,就能彻底避免死锁,核心优化思路:打散循环等待、减少锁嵌套、可控抢锁、规范编码。

2、四大生产落地解决方案(面试必背)

统一锁的加锁顺序(最核心、最简单)

破坏循环等待条件:所有线程严格按照固定顺序获取锁,例如永远先锁A、再锁B,杜绝线程交叉反向加锁,从根源打破环状依赖,是生产最常用、零成本规避方案。

减少锁嵌套、降低锁粒度

破坏请求且保持条件:尽量不嵌套加锁,杜绝一把锁不释放、再申请另一把锁;缩小锁范围,同步代码块只包裹核心竞争代码,减少长时间持有多把锁的场景。

使用限时锁、主动放弃抢占

破坏不可剥夺条件:放弃synchronized不可中断死等特性,使用ReentrantLock的tryLock(time)限时抢锁;规定时间内抢不到锁,主动释放已持有锁、退出竞争,避免永久阻塞等待。

避免长时间持有锁+资源合理分配

破坏互斥占用:锁内业务精简,不做耗时IO、远程调用、复杂计算;快速加锁、快速释放,减少锁持有时长,降低锁竞争僵持概率。

3、辅助优化手段(面试加分)

① 禁止随意嵌套锁,业务编码严控双层及以上锁嵌套;

② 定时检测线程状态,线上使用jstack排查死锁;

③ 复杂并发场景优先使用显式锁,灵活可控优于synchronized;

④ 业务拆分,拆分竞争资源,减少多线程争抢同一批锁资源。

4、通俗背诵口诀顺序加锁破循环、减少嵌套破持有、限时抢锁破剥夺、缩短耗时破互斥。

5、一句话总结统一顺序、少嵌锁、限时抢、快释放;破坏四大死锁条件,生产彻底规避死锁卡死。

3.死锁排查命令?(满分完整版+生产实操步骤+面试必背)

1、核心常用命令(面试必考)

jstack -l pid :最核心排查命令,打印线程堆栈信息,底部关键词 Found one Java-level deadlock 精准定位死锁线程、锁对象、代码行数;

jps -l:查询Java进程pid,快速获取需要排查的进程号;

jmap:排查内存快照,辅助分析死锁伴随的内存占用异常;

jcmd pid Thread.print:JDK1.8+新版命令,替代jstack,打印线程堆栈、死锁信息。

2、生产完整排查步骤(实操流程)

① 命令 jps -l 获取项目运行pid;

② 命令 jstack -l pid > deadlock.log 导出堆栈日志;

③ 搜索关键字 deadlock,定位死锁线程、锁资源、代码位置;

④ 分析线程互相持有、等待的锁对象,梳理循环依赖链路;

⑤ 修改代码、调整加锁顺序,重启服务修复。

3、可视化排查工具

① JDK自带:jvisualvm 图形化监控线程,直观查看死锁标记;

② 第三方:Arthas线上诊断,thread命令一键检测死锁、打印线程栈。

4、高频面试坑点

① jstack不仅排查死锁,还可排查线程阻塞、死循环、CPU飙高;

② 死锁日志固定在文件最底部,直接搜索deadlock最快定位;

③ 线上禁止频繁dump堆栈,瞬间卡顿影响服务性能。

5、一句话总结jps查进程、jstack抓堆栈、搜deadlock定位死锁;线上可用Arthas一键排查,精准定位代码行。

4.死锁、活锁、饥饿区别?(满分完整版+对比表格+通俗口诀+面试必考)

1、一句话极简背诵

死锁互相僵持永久阻塞、活锁反复重试一直失败、饥饿长期抢不到锁;三者都是线程异常阻塞问题,优先级:死锁危害最大、饥饿次之、活锁最轻。

2、三者详细定义(面试必背)

死锁(Deadlock) :多条线程互相持有对方需要的锁,互相无限等待、僵持不动,线程全部阻塞,永不释放、永不执行,无任何执行进展。

活锁(Livelock) :线程不会阻塞挂起,一直主动重试获取资源、频繁切换状态,但是互相谦让、一直抢占失败,忙碌空转、无法执行业务,代码一直跑但是没有结果。

线程饥饿(Starvation) :部分线程优先级低、排队靠后,一直被高优先级线程抢占资源,长期抢不到锁,迟迟无法执行、长期闲置,极端情况下永久得不到执行机会。

3、三者核心区别汇总表格

对比维度 死锁 活锁 线程饥饿
线程状态 永久阻塞、静止挂起 一直运行、频繁空转 长期等待、极少执行
阻塞状态 阻塞不释放 活跃无阻塞 长期排队无执行
产生原因 循环等待 + 互相持有锁 互相谦让、反复抢占失败 非公平锁、优先级差异
典型案例 双线程互相持有对方锁 两条线程互相主动退让锁 低优先级线程一直抢不到锁
是否自愈 无法自愈、永久卡死 极端情况可自行恢复 不会自愈、长期等待
危害程度 最高、服务卡死 中等、CPU 空转 最低、业务延迟

4、通俗直白比喻

① 死锁:两个人面对面站死,互相等着对方让路,谁都不动;

② 活锁:两个人互相礼貌让路,左右反复横跳,一直错开永远走不过去;

③ 饥饿:弱小的人一直被插队,永远排不到自己通行。

5、生产解决方案

① 解决死锁:统一加锁顺序、破坏四大必要条件;

② 解决活锁:增加随机重试时间,避免线程同步谦让;

③ 解决饥饿:使用公平锁,保证先来先执行,杜绝插队。

6、一句话终极总结死锁卡死不动、活锁空转忙等、饥饿长期插队;死锁最致命、公平锁根治饥饿、随机延时规避活锁。

5.什么是乐观锁悲观锁?(满分完整版+对比表格+面试必考)

1、一句话极简背诵

悲观锁默认并发冲突、全程加锁互斥、串行执行;乐观锁默认无冲突、不加锁、版本校验后置,冲突才重试。

2、悲观锁(Pessimistic Lock)

核心思想:极度悲观,默认多线程并发一定会产生资源竞争,访问资源前先加锁,独占资源,防止并发修改。

执行特点:独占排他、串行执行、线程阻塞、安全性高、并发性能差。

典型实现:synchronized、ReentrantLock、数据库行锁/表锁。

适用场景:写多读少、竞争激烈、频繁修改、数据一致性要求极高的场景。

3、乐观锁(Optimistic Lock)

核心思想:极度乐观,默认线程之间无资源竞争,无需加锁;更新数据时校验版本号/标记,判断是否被其他线程修改,冲突则重试或放弃。

执行特点:无锁并发、并行执行、无阻塞、性能高、存在重试开销。

典型实现:CAS无锁算法、数据库版本号锁、ConcurrentHashMap、Atomic原子类。

适用场景:读多写少、竞争微弱、追求高并发吞吐量、允许少量冲突重试的场景。

4、两者核心区别对比(面试必背表格)

对比维度 悲观锁 乐观锁
加锁时机 访问资源前提前加锁,全程独占资源 仅更新数据时做版本校验,全程无锁
核心并发思想 默认多线程并发一定会产生资源冲突,提前加锁规避风险 默认线程之间无资源竞争,无锁并行,冲突后再重试处理
线程执行状态 未抢到锁的线程阻塞挂起,任务串行执行 无阻塞挂起,多线程并行执行,仅冲突时自旋重试
代码实现成本 简单粗暴,底层已封装加锁释放逻辑,无需额外处理冲突 需自行处理冲突重试、版本校验逻辑,代码实现复杂度更高
核心适用场景 写多读少、资源竞争激烈、数据一致性要求极高的场景 读多写少、并发量极大、资源竞争微弱、追求高吞吐量的场景
系统性能开销 线程内核态阻塞挂起,上下文切换开销大,系统吞吐量低 仅冲突时 CPU 自旋重试,无阻塞挂起开销,系统并发性能更高

5、高频面试坑点

① CAS乐观锁存在ABA问题,需加版本号标记规避;

② 悲观锁阻塞挂起、耗费内核态资源;乐观锁自旋占用CPU,大量冲突性能暴跌;

③ MySQL中InnoDB默认行级悲观锁,也可手动实现版本号乐观锁;

④ Java中synchronized是悲观锁,Atomic类、CAS属于乐观锁。

6、一句话总结悲观锁上锁防冲突、安全低速;乐观锁无锁验冲突、高速容错;写多用悲观、读多用乐观。

6.分布式并发常用方案?(满分完整版+五大方案+生产代码实现)

1、一句话极简背诵

分布式并发核心解决:多服务多节点共享资源竞争、流量洪峰、重复提交、数据一致性

五大核心方案:分布式锁、限流熔断、幂等防重、消息队列削峰、分布式事务。

2、五大核心方案详解+代码实现(面试必考+生产落地)

方案一:分布式锁(解决跨节点资源竞争)

① 核心作用:控制分布式环境下多节点争抢同一共享资源,保证同一时间只有一个节点执行业务,替代本地synchronized/Lock;

② 常用实现:Redisson分布式锁(主流)、Zookeeper锁、Redis原生SETNX锁;

③ 生产代码(Redisson最简通用版):

java 复制代码
// 1、注入Redisson客户端
@Autowired
private RedissonClient redissonClient;

// 2、分布式锁业务实现
public void deductStock() {
    // 定义锁key,全局唯一
    String lockKey = "stock:lock:1001";
    RLock lock = redissonClient.getLock(lockKey);
    try {
        // 加锁:等待3s,持有锁10s自动过期
        boolean isLock = lock.tryLock(3, 10, TimeUnit.SECONDS);
        if (isLock) {
            // 执行扣库存、修改共享数据等并发业务
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    } finally {
        // 释放锁,防止死锁
        if (lock.isLocked() && lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}

方案二:流量限流(防止服务被流量打崩)

① 核心作用:限制接口访问频次,拦截恶意流量、突发洪峰,保护服务稳定性;分为单机限流、分布式限流;

② 常用算法:令牌桶、漏桶、滑动时间窗口;常用组件:Sentinel、Gateway限流;

③ 生产代码(Sentinel注解限流):

java 复制代码
// 接口限流,每秒最大访问5次
@SentinelResource(value = "order:create", blockHandler = "flowBlockHandler")
@GetMapping("/createOrder")
public String createOrder() {
    return "下单成功";
}
// 限流降级兜底方法
public String flowBlockHandler(BlockException e) {
    return "系统繁忙,请稍后重试";
}

方案三:接口幂等(防止重复提交、重复扣款)

① 核心作用:同一请求多次提交,服务只处理一次,杜绝重复下单、重复扣款、重复数据入库; ② 常用实现:唯一Token、数据库唯一索引、分布式唯一ID、状态机判断;

③ 生产代码(Token幂等防重):

java 复制代码
// 下单前获取唯一Token
@GetMapping("/getToken")
public String getToken() {
    String token = UUID.randomUUID().toString();
    // Token存入Redis,有效期5分钟
    redisTemplate.opsForValue().set("idempotent:token:"+token, "1",5,TimeUnit.MINUTES);
    return token;
}
// 幂等下单接口
@PostMapping("/order")
public String order(String token) {
    // 删除Token,删除成功则首次请求,失败则重复请求
    Boolean delete = redisTemplate.delete("idempotent:token:" + token);
    if (!delete) {
        return "请勿重复提交";
    }
    // 执行业务逻辑
    return "下单成功";
}

方案四:消息队列削峰填谷(异步解耦、抗高并发)

① 核心作用:瞬时高流量存入MQ队列,同步转异步,削平流量峰值,防止同步接口卡顿超时;适配秒杀、批量通知、异步日志;

② 常用组件:RabbitMQ、RocketMQ、Kafka;

③ 极简业务代码:

java 复制代码
// 生产者:接收请求,发送消息至队列,快速响应
@Autowired
private RocketMQTemplate rocketMQTemplate;
@GetMapping("/seckill")
public String seckill(Long goodsId) {
    rocketMQTemplate.syncSend("seckill_topic", goodsId);
    return "排队中,抢购成功稍后通知";
}
// 消费者:异步消费,扣库存、生成订单
@RocketMQMessageListener(topic = "seckill_topic", consumerGroup = "seckill_group")
public class SeckillConsumer implements MessageListenerConcurrently<Long> {
    @Override
    public ConsumeResult consumeMessage(List<MessageExt<Long>> list, ConsumeContext consumeContext) {
        // 异步执行业务,无阻塞高吞吐
        return ConsumeResult.SUCCESS;
    }
}

方案五:分布式事务(保证多服务数据一致)

① 核心作用:解决微服务跨库、跨服务数据一致性问题,防止事务回滚导致数据错乱;

② 常用方案:Seata AT模式(无侵入常用)、TCC、SAGA、本地消息表;

③ 核心使用注解:

java 复制代码
// 全局事务开启,跨服务保证数据一致
@GlobalTransactional(rollbackFor = Exception.class)
public void createGlobalOrder() {
    // 1、创建订单
    // 2、扣减库存
    // 3、账户扣款
}

3、面试高频选型规则(必背)

① 资源竞争、防止超卖 → 分布式锁

② 流量过大、防雪崩 → 限流熔断

③ 重复提交、重复扣款 → 接口幂等

④ 瞬时洪峰、异步业务 → 消息队列削峰

⑤ 微服务跨库、数据一致 → 分布式事务

4、高频面试坑点

① Redis分布式锁存在主从延时丢锁问题,生产优先Redisson看门狗续期;

② 幂等必须加过期时间,防止Redis无限堆积脏数据;

③ MQ必须配置消息确认、死信队列,防止消息丢失;

④ 分布式锁禁止使用本地锁,多节点完全失效。

5、一句话总结争抢用分布式锁、流量用限流、重复用幂等、洪峰用MQ、跨服务用事务;五大方案组合,覆盖全部分布式并发场景。

7.CompletableFuture 作用?(满分完整版+底层+链式编排+面试必考)

1、一句话极简背诵

JDK8推出的异步任务编排类,替代传统Future,解决回调地狱、手动阻塞等待问题;支持链式编程、多任务组合、异步回调,优雅实现复杂多线程业务编排。

2、传统Future痛点(面试对比加分)

① 获取结果必须阻塞/轮询,代码卡顿、效率低;

② 无回调机制,任务结束无法自动通知;

③ 多任务嵌套串行,代码臃肿、回调地狱;

④ 无法灵活组合多个异步任务。

3、五大核心作用(面试必背)

非阻塞回调:任务执行完毕自动回调方法,无需主动阻塞get(),不浪费CPU;

链式流式编排:采用Lambda链式写法,代码简洁流畅,彻底消灭回调地狱;

多任务灵活组合:提供allOf()全部等待、anyOf()任意一个完成,精准管控多线程执行逻辑;

异常自动捕获处理:内置exceptionally()异常回调,无需手动try-catch捕获线程异常;

线程切换灵活:支持当前线程、自定义线程池切换执行任务,灵活管控执行载体。

4、常用核心方法

① 异步执行:supplyAsync(有返回值)、runAsync(无返回值);

② 结果回调:thenApply、thenAccept、thenRun;

③ 任务组合:allOf(全部完成)、anyOf(任一完成);

④ 异常兜底:exceptionally异常补偿、whenComplete最终执行。

5、极简代码演示

java 复制代码
// 异步执行+链式回调,无阻塞、无回调地狱
CompletableFuture.supplyAsync(() -> {
    // 异步耗时任务
    return "异步查询结果";
}).thenAccept(res -> {
    // 任务完成自动回调处理结果
    System.out.println(res);
}).exceptionally(e -> {
    // 异常兜底处理
    return null;
});

6、生产高频使用场景

① 多接口并行查询、批量异步调用,缩短接口响应时间;

② 复杂业务任务拆分、异步编排执行;

③ 无需同步等待结果的后台异步任务;

④ 替代原始Thread+Future,简化异步编码。

7、高频面试坑点

① 默认使用ForkJoinPool公共线程池,IO密集任务建议自定义线程池隔离,防止公共池被耗尽; ② get()方法会阻塞线程,生产优先使用异步回调,禁止主动阻塞;

③ 多任务编排优先allOf/anyOf,不要嵌套多层CompletableFuture;

④ 必须捕获异常,否则异步异常静默丢失,排查困难。

8、一句话总结弥补Future阻塞缺陷、链式异步编排、支持多任务组合、自带异常回调;简化异步代码、消灭回调地狱,生产异步业务首选。

8.结构化并发 StructuredTaskScope 作用?(JDK21+满分完整版+底层+面试必考)

1、一句话极简背诵

JDK21推出的结构化并发工具类,统一管控一批子线程生命周期,父子线程绑定、异常联动、自动取消、无线程泄露,解决传统异步线程分散失控、排查困难、资源泄露痛点,搭配虚拟线程最佳。

2、四大核心作用(面试必背)

生命周期绑定:父线程创建的所有子线程统一纳入管控,父线程等待全部子线程执行完毕后才结束,杜绝线程泄露、残留后台幽灵线程;

异常联动传播:任意一条子线程抛出异常,自动取消其他未完成子线程,快速失败、避免无效任务空转,防止资源浪费;

自动回收资源:无需手动关闭线程、无需兜底捕获,作用域结束自动销毁所有子线程,彻底规避内存泄漏、句柄残留;

链路清晰、排查简单:线程层级关系明确,堆栈信息可追溯父子线程调用链路,解决异步线程日志混乱、线上排查难问题。

3、底层设计思想 遵循结构化并发编程思想:任务有边界、线程有归属、异常可联动、资源必回收;替代手动CountDownLatch、CompletableFuture批量管控,简化多任务编排。

4、极简代码演示

java 复制代码
// 创建结构化任务作用域,统一管控子线程
try(StructuredTaskScope<String> scope = new StructuredTaskScope<>()){
    // 提交多条异步子任务
    StructuredTaskScope.Subtask<String> task1 = scope.fork(() -> 接口查询1);
    StructuredTaskScope.Subtask<String> task2 = scope.fork(() -> 接口查询2);
    // 等待所有子任务执行完毕
    scope.join();
    // 获取结果,任意任务异常自动取消其他任务
}

5、高频面试坑点

① JDK21及以上专属,专为虚拟线程优化,物理线程同样兼容;

② try-with-resources语法自动关闭作用域,无需手动销毁线程;

③ 区别CompletableFuture:主打线程生命周期管控、异常联动取消,杜绝泄露;

④ 生产适合批量并行查询、多接口聚合、多级异步编排业务。

6、一句话总结父子线程绑定、统一生命周期、异常联动取消、自动回收资源;专治异步线程泄露、链路混乱,JDK21结构化并发官方标准方案。

9.高并发接口优化手段?(满分完整版+七层优化+企业实战代码+生产配置)

1、一句话极简背诵

高并发优化核心:抗流量、降耗时、削峰值、保稳定

分层优化:前端防重、网关限流、本地缓存+分布式缓存、异步解耦、数据库优化、锁优化、兜底降级,层层拦截流量。

2、七层完整优化方案(企业标准架构)

第一层:前端优化(拦截无效流量)

① 页面静态化、CDN加速,静态资源缓存至边缘节点;

② 按钮防抖、禁止重复提交、前端本地缓存;

③ 图片/资源压缩、懒加载,减少请求频次与请求体积。

第二层:网关层优化(流量第一道关卡)

① 网关统一限流、黑名单拦截、恶意IP封禁;

② 接口权限校验、请求参数脱敏、非法请求过滤;

③ 路由负载均衡,Nginx+网关实现请求分发,避免单点压力。

第三层:应用层优化(代码核心优化)

缓存优化:多级缓存(本地缓存Caffeine+Redis分布式缓存),减少DB查询;

异步解耦:CompletableFuture异步编排、MQ削峰,同步转异步,缩短接口响应;

线程池优化:区分CPU/IO密集线程池,隔离业务线程,避免线程互相抢占;

锁优化:缩小锁粒度、禁用悲观锁、高并发使用ReentrantLock、规避死锁;

接口幂等:Token+唯一索引,防止重复提交、重复扣款。

第四层:缓存层优化(减少DB压力)

① 多级缓存:本地缓存优先查询,未命中再查Redis,最后查数据库;

② 缓存策略:热点数据永不过期、定时更新,冷门数据设置过期淘汰;

③ 缓存问题解决:缓存穿透(布隆过滤器)、缓存击穿(互斥锁+热点永不过期)、缓存雪崩(过期随机+集群部署)。

第五层:数据库优化(最终承压层)

① SQL优化:禁止大字段查询、避免联表、加合理索引、防止慢SQL;

② 读写分离:主库写、从库读,拆分读写流量;

③ 分库分表:大数据量水平分表,单表数据控制在1000w以内;

④ 连接池优化:调整MySQL连接数,避免连接耗尽。

第六层:流量管控(防止服务雪崩)

① 限流:Sentinel令牌桶限流、网关限流,限制QPS;

② 降级:非核心业务降级、返回兜底数据,保障核心流程;

③ 熔断:依赖服务异常熔断,防止级联雪崩。

第七层:架构优化(海量流量终极方案)

① 服务集群:多实例部署,负载均衡分摊流量;

② 异地多活、机房容灾,防止单点机房故障;

③ 业务拆分:微服务拆分,业务隔离,故障不扩散。

3、企业开发实战代码(可直接上线)

① 多级缓存实战(Caffeine+Redis)

java 复制代码
// 1、本地缓存Caffeine配置(高性能本地缓存)
@Bean
public Cache<Long, GoodsInfo> caffeineCache() {
    return Caffeine.newBuilder()
            .maximumSize(10000) // 最大缓存数量
            .expireAfterWrite(3, TimeUnit.MINUTES) // 写入过期
            .recordStats() // 统计缓存命中率
            .build();
}

// 2、业务查询多级缓存执行逻辑
@Autowired
private Cache<Long, GoodsInfo> localCache;
@Autowired
private StringRedisTemplate redisTemplate;

public GoodsInfo getGoodsInfo(Long goodsId){
    // 1、查询本地缓存
    GoodsInfo local = localCache.getIfPresent(goodsId);
    if(Objects.nonNull(local)) return local;
    // 2、查询Redis分布式缓存
    String redisKey = "goods:info:" + goodsId;
    String json = redisTemplate.opsForValue().get(redisKey);
    if(StringUtils.hasNotText(json)){
        GoodsInfo goodsInfo = JSON.parseObject(json, GoodsInfo.class);
        localCache.put(goodsId,goodsInfo); // 回填本地缓存
        return goodsInfo;
    }
    // 3、查询数据库+回填缓存
    GoodsInfo dbInfo = goodsMapper.selectById(goodsId);
    if(Objects.isNull(dbInfo)) return null;
    redisTemplate.opsForValue().set(redisKey,JSON.toJSONString(dbInfo),5,TimeUnit.MINUTES);
    localCache.put(goodsId,dbInfo);
    return dbInfo;
}

② Sentinel限流降级实战(生产通用)

java 复制代码
// 接口限流+降级,高并发保护接口
@SentinelResource(value = "goods:detail", blockHandler = "flowHandler", fallback = "fallbackHandler")
@GetMapping("/goods/detail/{id}")
public Result<GoodsInfo> getDetail(@PathVariable Long id){
    // 正常业务查询逻辑
    return Result.success(goodsService.getGoodsInfo(id));
}
// 限流兜底:流量超限触发
public Result<GoodsInfo> flowHandler(Long id, BlockException e){
    return Result.fail("访问人数过多,请稍后重试");
}
// 降级兜底:业务异常触发
public Result<GoodsInfo> fallbackHandler(Long id, Throwable e){
    return Result.fail("服务繁忙,临时降级兜底");
}

③ CompletableFuture异步并行优化(缩短接口耗时)

java 复制代码
// 多接口并行查询,串行500ms+,并行压缩至150ms
@Override
public Result<GoodsDetailVO> getGoodsDetailAsync(Long goodsId) {
    // 1、并行执行三个耗时查询
    CompletableFuture<GoodsInfo> infoFuture = CompletableFuture.supplyAsync(() -> goodsMapper.selectById(goodsId), goodsThreadPool);
    CompletableFuture<List<GoodsSku>> skuFuture = CompletableFuture.supplyAsync(() -> skuMapper.selectList(goodsId), goodsThreadPool);
    CompletableFuture<GoodsStock> stockFuture = CompletableFuture.supplyAsync(() -> stockMapper.selectById(goodsId), goodsThreadPool);
    // 2、全部任务执行完毕,汇总结果
    CompletableFuture.allOf(infoFuture,skuFuture,stockFuture).join();
    // 3、封装返回数据
    GoodsDetailVO vo = new GoodsDetailVO();
    vo.setGoodsInfo(infoFuture.join());
    vo.setSkuList(skuFuture.join());
    vo.setStock(stockFuture.join());
    return Result.success(vo);
}

④ Redis布隆过滤器解决缓存穿透

java 复制代码
// 拦截不存在的商品ID,防止穿透DB
@Bean
public BloomFilter<Long> goodsBloomFilter(){
    return BloomFilter.simple(1000000,0.01);
}
// 业务使用:查询前先校验
if(!bloomFilter.contains(goodsId)){
    return Result.fail("商品不存在");
}

4、生产避坑规范(面试高频)

① 禁止大事务、长事务,事务内不做远程调用、循环查询;

② 缓存设置过期时间,热点数据定时刷新,避免缓存一致性问题;

③ 线程池务必自定义,禁止使用Executors,隔离业务线程;

④ 限流降级必须配置兜底策略,避免前端报错白屏;

⑤ 高并发接口禁止频繁打印日志、禁止序列化大对象。

5、一句话终极总结七层分层拦截流量、多级缓存压DB、异步并行缩耗时、限流降级保稳定、规范编码避坑;以上代码全部生产通用,可直接落地企业高并发项目。

10.多线程面试最终总结核心?(满分终极总结+背诵万能话术+逻辑闭环)

1、核心底层逻辑

多线程本质就是利用CPU资源、切换任务执行;进程隔离资源,线程共享资源;并发交替执行、并行同时执行。所有线程底层依托JVM+操作系统调度,靠上下文切换实现多任务运行。

2、线程核心基础

线程七种生命周期,重点区分RUNNABLE、BLOCKED、WAITING、TIMED_WAITING;

sleep休眠不释放锁、wait等待主动释放锁;

线程终止优先volatile标记位+interrupt中断,禁止stop暴力销毁;

守护线程依附用户线程消亡,仅做后台监控。

3、锁体系核心

① synchronized:JDK内置悲观锁,自动加解锁,底层依靠对象头MarkWord+Monitor,存在无锁→偏向锁→轻量级锁→重量级锁升级机制,低并发性能优秀;

② Lock显式锁:基于AQS实现,手动加解锁,支持公平锁、超时锁、中断锁、精准唤醒,高并发性能更强;

③ 锁通用规范:尽量缩小锁粒度、避免锁嵌套、统一加锁顺序,防止死锁。

4、JUC常用工具

CAS无锁乐观锁解决轻量并发,存在ABA问题;

线程池七大参数必须熟记,生产禁止Executors,手动自定义有界队列线程池;区分CPU密集、IO密集线程池配置;

Atomic原子类、并发容器保证线程安全。

5、异步与新版特性

CompletableFuture实现异步任务链式编排,解决Future阻塞痛点;

JDK21推出虚拟线程+结构化并发+ScopedValue,轻量化管控线程、杜绝内存泄漏,专为IO密集业务优化。

6、并发常见问题与解决方案

死锁四大必要条件,破坏任意一条即可规避;死锁、活锁、饥饿三者区分清晰;

高并发三大缓存问题:穿透、击穿、雪崩,配套对应解决方案;

分布式并发依靠分布式锁、限流、幂等、MQ、分布式事务保障业务稳定。

7、生产编码硬性规范(面试必考兜底)

① 业务优先线程池,禁止手动new Thread;

② 低并发用synchronized,高并发复杂场景用ReentrantLock;

③ 减少上下文切换、避免频繁创建销毁线程;

④ 禁止死锁、禁止暴力销毁线程、禁止无界队列;

⑤ 高并发采用多级缓存、异步解耦、限流降级架构优化。

8、一句话终极面试万能总结多线程核心就是合理抢占CPU、安全共享资源、规范管控线程;基础看懂生命周期、锁吃透两套体系、线程池规范使用、并发问题对症下药,兼顾性能与线程安全,适配生产高并发业务。

相关推荐
XiYang-DING8 小时前
【Java EE】TCP—流量控制和拥塞控制
java·tcp/ip·java-ee
无限进步_8 小时前
C++异常机制:抛出、捕获与栈展开
开发语言·c++·安全
小白学大数据8 小时前
深度探索:Python 爬虫实现豆瓣音乐全站采集
开发语言·爬虫·python·数据分析
Xin_ye100868 小时前
C# 零基础到精通教程 - 第八章:面向对象编程(进阶)——继承与多态
开发语言·c#
m0_748839498 小时前
R包grafify:简单操作实现高效统计绘图
开发语言·r语言
BIG_PEI8 小时前
检查并安装Redis
java
大貔貅喝啤酒8 小时前
基于Windows下载安装Android Studio 3.3.2版本教程(2026详细图文版)
android·java·windows·android studio
Evand J8 小时前
【课题推荐与代码介绍】卡尔曼滤波器正反向估计算法原理与MATLAB实现
开发语言·算法·matlab
奋斗的小方8 小时前
Java基础篇09:项目实战
java·开发语言