通关我贼溜
155.11M · 2026-02-19
多线程就是在同一进程内同时存在多个执行流(线程),每个线程都有自己的执行路径、程序计数器和栈,但共享进程的堆内存与全局资源。
在 Java 中,线程可以通过继承 Thread 或实现 Runnable/Callable(并配合 ExecutorService)来创建和管理。
关键点:
单核 CPU 可以支持多线程:通过 时间片轮转(time-slicing) ,OS 在多个线程之间快速切换,使得从用户角度看多个线程“同时”运行。
并行: 多核 CPU 下,多个线程同时在不同核心上执行(真正的 “同时”);
并发: 单核 CPU 下,操作系统通过 “时间片轮转” 机制,快速切换不同线程的执行(比如每个线程执行 10ms,切换速度极快,肉眼感觉像 “同时”)。
例如:
单核 CPU 下,你一边听歌(音频线程),一边写代码(编辑器线程),操作系统会快速在这两个线程间切换,你感觉是同时进行的,但实际上 CPU 同一时刻只执行一个线程。
不一定,甚至可能更低:
效率高的场景:线程中有大量 “IO 操作”(比如网络请求、文件读写),此时 CPU 会闲置,多线程可以利用闲置时间执行其他线程。比如:
效率低的场景:线程都是 “CPU 密集型”(比如大量计算,无 IO 等待),此时多线程会增加 “线程切换开销”(保存线程上下文、恢复上下文),反而比单线程慢。比如:
死锁是多线程中最典型的问题,指多个线程互相持有对方需要的资源,且都不释放自己持有的资源,导致所有线程都无法继续执行。
经典产生条件(四个必要条件):
public class DeadlockDemo {
private static final Object lockA = new Object();
private static final Object lockB = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lockA) {
System.out.println("t1 持有 lockA");
sleep(100); // 模拟工作,确保顺序
synchronized (lockB) {
System.out.println("t1 持有 lockB");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lockB) {
System.out.println("t2 持有 lockB");
sleep(100);
synchronized (lockA) {
System.out.println("t2 持有 lockA");
}
}
});
t1.start();
t2.start();
}
private static void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException ignored) {}
}
}
t1 先锁 lockA 再想要 lockB;t2 先锁 lockB 再想要 lockA —— 如果两个都在各自的第一把锁后等待第二把锁,就会死锁。
固定锁获取顺序(最简单且常用) :对多个资源设定全局顺序,所有线程都按相同顺序获取锁(例如先锁 A 再锁 B),这样就消除了循环等待条件。
减少锁的持有时间:尽量把同步块变窄,只保护必要的临界区。
使用 tryLock(带超时)而不是阻塞式获取(适用于 java.util.concurrent.locks.Lock):
如果一段时间内获取不到锁,就释放已持有的锁、退避并重试,从而避免无限等待。
例子:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
Lock la = new ReentrantLock();
Lock lb = new ReentrantLock();
boolean safeAcquire() throws InterruptedException {
while (true) {
boolean aLocked = la.tryLock(50, TimeUnit.MILLISECONDS);
if (!aLocked) continue;
try {
boolean bLocked = lb.tryLock(50, TimeUnit.MILLISECONDS);
if (!bLocked) {
// 无法获得第二把锁,释放第一把,稍后重试
la.unlock();
Thread.sleep(10);
continue;
}
// 两把锁都拿到了
return true;
} finally {
// 在使用完成后解锁(调用者负责)
}
}
}
使用更高级的并发数据结构/算法:ConcurrentHashMap、BlockingQueue 等并发容器通常内部已避免复杂锁竞争。
尽可能使用不可变对象或无锁算法:减少需要加锁的场景。
避免嵌套锁或减少锁的层级:尽量不在持有多个锁时调用外部可阻塞的方法。
用单线程执行关键段(actor 模型 / 消息队列) :把需要串行化的操作发送到单独线程处理(例如事件队列),从而避免锁。
jstack、JVisualVM、ThreadMXBean.findDeadlockedThreads() 等工具。