当前位置: 首页 > news >正文

【Java后端】Java 多线程:从原理到实战,再到高频面试题

Java 多线程:从原理到实战,再到高频面试题

这是一篇“可运行 + 可面试”的多线程文章:先讲原理,再给代码,最后用面试题查漏补缺。你可以把文中的代码复制到一个独立的 Maven/Gradle 项目中运行(JDK 8+)。


一、并发与并行、进程与线程

  • 并发(Concurrency):同一时间段内交替处理多个任务。
  • 并行(Parallelism):同一时刻同时处理多个任务(需要多核)。
  • 进程:资源分配的基本单位。
  • 线程:CPU 调度的基本单位,同一进程内线程共享堆和方法区,但拥有独立的栈和程序计数器。

Java 内存模型(JMM)三个关键词

  • 可见性:一个线程对共享变量的写,是否能被其他线程“看见”。
  • 有序性:指令是否可以重排。
  • 原子性:操作是否不可分割。

二、创建线程的 4 种常见方式

1)继承 Thread

public class HelloThread extends Thread {@Overridepublic void run() {System.out.println("Hello from " + Thread.currentThread().getName());}public static void main(String[] args) {new HelloThread().start();}
}

优缺点:简单但受限(Java 单继承),不利于任务与线程的解耦。

2)实现 Runnable

public class HelloRunnable implements Runnable {@Overridepublic void run() {System.out.println("Hello from " + Thread.currentThread().getName());}public static void main(String[] args) {Thread t = new Thread(new HelloRunnable());t.start();}
}

优点:任务与线程解耦,适合复用与线程池。

3)实现 Callable<V> + Future

import java.util.concurrent.*;public class SumTask implements Callable<Integer> {private final int n;public SumTask(int n) { this.n = n; }@Overridepublic Integer call() {int sum = 0;for (int i = 1; i <= n; i++) sum += i;return sum;}public static void main(String[] args) throws Exception {ExecutorService pool = Executors.newSingleThreadExecutor();Future<Integer> f = pool.submit(new SumTask(100));System.out.println("result=" + f.get());pool.shutdown();}
}

亮点:可以有返回值、可抛出受检异常。

4)线程池 ExecutorService

import java.util.concurrent.*;public class PoolDemo {public static void main(String[] args) throws InterruptedException {ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 4,60, TimeUnit.SECONDS,new LinkedBlockingQueue<>(100),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());for (int i = 0; i < 10; i++) {int id = i;pool.execute(() -> System.out.println("task-" + id + " -> " + Thread.currentThread().getName()));}pool.shutdown();pool.awaitTermination(1, TimeUnit.MINUTES);}
}

建议:生产中优先手动构造 ThreadPoolExecutor,不要直接用 Executors.newFixedThreadPool(...) 等默认策略(可能造成 OOM)。


三、关键字 synchronized 与可重入锁 ReentrantLock

1)synchronized 基本用法

public class Counter {private int c = 0;public synchronized void inc() { c++; }public synchronized int get() { return c; }
}
  • 监视器锁(对象锁/类锁),可重入
  • 可见性原子性得到保障,synchronized 的释放-获取建立 happens-before 关系。
对象锁 vs. 类锁
public class LockTypes {private static int s;private int i;public synchronized void objLock() { i++; }        // 对象锁public static synchronized void classLock() { s++; } // 类锁
}

2)ReentrantLockCondition

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.*;public class LockDemo {private final Lock lock = new ReentrantLock();private final Condition notEmpty = lock.newCondition();private String data;public void put(String v) {lock.lock();try {data = v;notEmpty.signal();} finally { lock.unlock(); }}public String take() throws InterruptedException {lock.lock();try {while (data == null) notEmpty.await(1, TimeUnit.SECONDS);String v = data; data = null; return v;} finally { lock.unlock(); }}
}

优势

  • 可中断获取锁 lockInterruptibly()
  • 可定时尝试 tryLock(timeout)
  • 支持多个条件队列 Condition

选型建议:简单临界区用 synchronized,需要定时/可中断/多个条件队列时用 ReentrantLock


四、volatile、原子类与 CAS

1)volatile

  • 保证 可见性禁止指令重排,但 不保证复合操作原子性
public class VolatileDemo {volatile boolean running = true;void stop() { running = false; }
}

2)原子类 AtomicInteger

import java.util.concurrent.atomic.AtomicInteger;public class AtomicDemo {private final AtomicInteger ai = new AtomicInteger();public void inc() { ai.incrementAndGet(); }public int get() { return ai.get(); }
}
  • 底层用 CAS(Compare-And-Swap) 实现无锁原子更新。

CAS 三件套:期望值、内存地址、目标新值;失败会自旋重试。注意 ABA 问题(可用 AtomicStampedReference)。


五、AQS 同步器家族(简版导图)

  • ReentrantLock:独占锁,AQS 独占模式
  • Semaphore:信号量,限流。
  • CountDownLatch:计数器,等待 N 个子任务完成。
  • CyclicBarrier:栅栏,N 线程相互等待,聚合再继续。
  • ReentrantReadWriteLock:读写锁,提高读多写少场景吞吐。

代码速览

// CountDownLatch:等待所有子任务完成
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {new Thread(() -> {try { Thread.sleep(500); } catch (InterruptedException ignored) {}latch.countDown();}).start();
}
latch.await();
System.out.println("all done");// CyclicBarrier:N 个线程到齐再继续
CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("go!"));
for (int i = 0; i < 3; i++) {new Thread(() -> {try { barrier.await(); } catch (Exception ignored) {}System.out.println(Thread.currentThread().getName()+" passed");}).start();
}// Semaphore:限流
Semaphore sem = new Semaphore(2);
for (int i = 0; i < 5; i++) {new Thread(() -> {try { sem.acquire();System.out.println(Thread.currentThread().getName()+" in");Thread.sleep(300);} catch (InterruptedException ignored) {}finally { sem.release(); }}).start();
}

六、阻塞队列与经典“生产者-消费者”

import java.util.concurrent.*;public class ProducerConsumer {private final BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);public void start() {ExecutorService pool = Executors.newCachedThreadPool();// Producerpool.execute(() -> {int i = 0;try {while (i < 20) {queue.put(i);System.out.println("P -> " + i);i++;}} catch (InterruptedException ignored) {}});// Consumerpool.execute(() -> {try {while (true) {Integer v = queue.take();System.out.println("C <- " + v);if (v == 19) break;}} catch (InterruptedException ignored) {}});pool.shutdown();}public static void main(String[] args) { new ProducerConsumer().start(); }
}

思路:用 BlockingQueue 自带的阻塞/唤醒语义避免手写 wait/notify


七、ThreadLocal 的使用与清理

public class TL {private static final ThreadLocal<String> TL = new ThreadLocal<>();public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {TL.set("trace-id-123");try {System.out.println(TL.get());} finally {TL.remove(); // 避免线程池线程复用导致的脏数据/内存泄漏}});t.start();t.join();}
}

场景:请求上下文、数据库连接、日期格式化器。注意在线程池中一定 remove()


八、死锁、活锁与饥饿

1)死锁复现

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class DeadLock {static final Lock A = new ReentrantLock();static final Lock B = new ReentrantLock();public static void main(String[] args) {new Thread(() -> holdThenWait(A, B), "T1").start();new Thread(() -> holdThenWait(B, A), "T2").start();}static void holdThenWait(Lock first, Lock second) {first.lock();try {sleep(100);second.lock(); // 两线程锁顺序相反 -> 死锁try { } finally { second.unlock(); }} finally { first.unlock(); }}static void sleep(long ms){ try{ Thread.sleep(ms);}catch(Exception ignored){} }
}

避免策略:统一加锁顺序、设置超时 tryLock(timeout)、死锁检测(jstack/可视化工具)。

2)活锁与饥饿

  • 活锁:都在不断“礼让”,却始终无法前进(比如不断重试但彼此让步)。
  • 饥饿:高优先级线程一直占用资源,低优先级线程长期得不到执行。

九、CompletableFuture:组合异步编程

import java.util.concurrent.*;public class CF {static String slow(String name) {try { Thread.sleep(300); } catch (InterruptedException ignored) {}return name + "@" + Thread.currentThread().getName();}public static void main(String[] args) {ExecutorService pool = Executors.newFixedThreadPool(3);CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> slow("A"), pool);CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> slow("B"), pool);CompletableFuture<String> both = f1.thenCombine(f2, (a, b) -> a + "+" + b);System.out.println(both.join());pool.shutdown();}
}

常用方法thenApply/thenAcceptthenCompose/thenCombineallOf/anyOfexceptionally/handle


十、线程池参数与拒绝策略(含调优建议)

  • 核心线程数 corePoolSize:长期保留的工作线程数。
  • 最大线程数 maximumPoolSize:任务堆积时允许的最大线程数。
  • 存活时间 keepAliveTime:非核心线程闲置回收时间。
  • 有界队列:推荐 ArrayBlockingQueue/LinkedBlockingQueue(capacity)
  • 拒绝策略:AbortPolicy/CallerRunsPolicy/DiscardPolicy/DiscardOldestPolicy

估算思路(CPU 密集 vs I/O 密集)

  • CPU 密集:core ≈ CPU核数CPU核数 + 1
  • I/O 密集:core ≈ CPU核数 × (1 + 平均等待时间/平均计算时间)

监控:采集 pool.getActiveCount()getQueue().size()、任务耗时分布,配合 Runtime.getRuntime().maxMemory() 观察内存占用。


十一、Wait/Notify vs 条件队列(附最小演示)

public class WaitNotifyDemo {private final Object lock = new Object();private boolean ready = false;public void signal() {synchronized (lock) {ready = true;lock.notifyAll();}}public void await() throws InterruptedException {synchronized (lock) {while (!ready) lock.wait();}}
}

对比wait/notify 容易误用(丢信号、虚假唤醒),相比之下 ConditionBlockingQueue 更安全可控。


十二、面试高频题(含简答)

Q1:synchronizedReentrantLock 区别?

  • 语法层面:关键字 vs API 类。
  • 功能:ReentrantLock 支持可中断、定时、多个 Conditionsynchronized 无需手动释放,JIT 可做锁消除/粗化/偏向等优化(JDK 不同版本行为不同)。
  • 性能:差距取决于场景与版本;简单场景优先 synchronized

Q2:volatile 能保证原子性吗?

:不能,i++ 仍需加锁或用原子类。volatile 保障可见性与有序性(禁止重排)。

Q3:什么是 happens-before

:JMM 中保证可见性的偏序关系,如:锁的释放→之后对同一锁的获取、volatile 写→读、线程启动前对变量的写→Thread.start() 后该线程可见、线程终止 join() 前对变量的写→join() 返回后可见。

Q4:什么是 ABA 问题?如何解决?

:CAS 中,值从 A→B→A,CAS 仍成功但期间发生变化。可用版本号(AtomicStampedReference)或加时序/指针不可复用策略解决。

Q5:线程池为什么不建议直接用 Executors.newFixedThreadPool()

:其队列是无界 LinkedBlockingQueue,在任务大量堆积时可能导致 OOM;建议自行指定有界队列与拒绝策略。

Q6:ThreadLocal 会内存泄漏吗?

:会。在线程池中线程长期存活,ThreadLocalMapEntry 使用弱引用指向 key,但 value 是强引用,需要手动 remove();否则可能泄漏或数据串线。

Q7:死锁产生的四个必要条件?

:互斥、占有且等待、不可剥夺、循环等待。破坏任一即可避免。

Q8:CountDownLatchCyclicBarrier 区别?

:前者一次性闭锁,减少到 0 即释放;后者可复用的屏障,固定 parties 个数,每轮到齐再放行。

Q9:CompletableFutureFuture 的区别?

Future 只能阻塞 getCompletableFuture 支持链式编排、组合、异常处理与回调,更适合复杂异步流程。

Q10:读写锁适用场景?

:读多写少、读操作占绝对多数,且读之间可以并行的场景。注意写锁会阻塞读,避免写偏斜。


十三、实战:批量并发调用 + 超时与降级

import java.util.*;
import java.util.concurrent.*;public class BulkCallDemo {static String call(int id) {try { Thread.sleep(200 + (id % 3) * 200); } catch (InterruptedException ignored) {}return "OK-" + id;}public static void main(String[] args) {ExecutorService pool = new ThreadPoolExecutor(8, 16, 60, TimeUnit.SECONDS,new ArrayBlockingQueue<>(100));List<CompletableFuture<String>> futures = new ArrayList<>();for (int i = 0; i < 10; i++) {int id = i;CompletableFuture<String> f = CompletableFuture.supplyAsync(() -> call(id), pool).orTimeout(400, TimeUnit.MILLISECONDS).exceptionally(ex -> "FALLBACK-" + id);futures.add(f);}List<String> results = futures.stream().map(CompletableFuture::join).toList();System.out.println(results);pool.shutdown();}
}

要点

  • 统一线程池,控制并发度
  • 每个任务设置单独超时
  • 异常统一降级,保持整体可用性

十四、排错与调试

  • jstack:线程栈与死锁检测。
  • jmap:堆转储,排查内存泄漏。
  • jconsole/visualvm/Java Flight Recorder:观察线程状态、CPU、锁竞争。

十五、最佳实践清单(可做 CR Checklist)

  1. 线程池必须有界队列 + 合理拒绝策略。
  2. 共享变量要么不共享,要么只读,要么用锁/原子类保护。
  3. 在线程池中使用 ThreadLocal 必须 try...finally remove()
  4. 锁要细化但不碎片化,统一加锁顺序,能降级就降级为读写锁。
  5. 慎用 volatile 做计数器;避免“写-读-改-写”竞态。
  6. 超时与中断是一等公民,API 支持就用起来(tryLock(timeout)orTimeout)。
  7. 日志打点线程名、traceId,关键路径上报拒绝次数、等待时长、任务耗时分位数。

至此,你已经具备:能写、能读、能调优、能答题的并发基础。如果你在用到具体业务(例如 I/O 密集的网关、计算密集的风控特征工程)时需要专项调参,可以在此文的代码骨架上再做针对性实验。

http://www.dtcms.com/a/349514.html

相关文章:

  • Claude Code 使用及配置智能体
  • 【科研绘图系列】R语言绘制代谢物与临床表型相关性的森林图
  • 从零到一:现代化充电桩App的React前端参考
  • 将FGUI的Shader全部预热后,WebGL平台没有加载成功
  • 基于MalConv的恶意软件检测系统设计与实现
  • 大模型 transformer 步骤
  • 《拉康精神分析学中的欲望辩证法:能指的拓扑学与主体的解构性重构》
  • 计算机大数据技术不会?医院体检数据可视化分析系统Django+Vue全栈方案
  • 不止效率工具:AI 在文化创作中如何重构 “灵感逻辑”?
  • 【DFS 或 BFS 或拓扑排序 - LeetCode】329. 矩阵中的最长递增路径
  • 【图像算法 - 23】工业应用:基于深度学习YOLO12与OpenCV的仪器仪表智能识别系统
  • 基于视觉的果园无人机导航:一种基于干预模仿学习与VAE控制器的真实世界验证
  • 机器人中的李代数是什么
  • 抖音多账号运营新范式:巨推AI如何解锁流量矩阵的商业密码
  • 量子计算驱动的Python医疗诊断编程前沿展望(下)
  • 数据结构:单向链表的逆置;双向循环链表;栈,输出栈,销毁栈;顺序表和链表的区别和优缺点;0825
  • 平安产险青海分公司启动2025年“乡风文明100行动” 首站落地海东市乐都区土官沟村
  • 【C++详解】哈希表概念与实现 开放定址法和链地址法、处理哈希冲突、哈希函数介绍
  • Redis缓存雪崩缓存击穿缓存穿透的处理方式
  • [React]Antd Upload组件上传多个文件
  • 阿里云安装postgre数据库
  • Vim 的 :term命令:终端集成的终极指南
  • 中介者模式及优化
  • Flink 状态 RocksDBListState(写入时的Merge优化)
  • 元宇宙与个人生活:重构日常体验的数字新维度
  • 技术攻坚与安全兜底——消防智能仓储立库管理系统的国产化硬核实力
  • ADB 调试工具的学习[特殊字符]
  • 性能优化:首屏加载速度的优化
  • Seaborn数据可视化实战:Seaborn高级使用与性能优化教程
  • C++编译链接与性能优化答案