一、线程与JVM的亲密关系
想象一下JVM是一个大工厂,线程就是工厂里干活的工人。每个工人都有自己的工具箱(线程栈),里面装着干活需要的工具(局部变量、方法调用记录)。JVM负责给这些工人分配工作空间,而线程就是在JVM这个地盘上奔跑的执行单元。
重点来了:每个Java线程启动时,JVM都会给它分配一块私有内存区域(线程栈),这块区域存储着:
- 方法调用栈帧(记录执行到哪一步了)
- 局部变量(比如临时计算的数字)
- 操作数栈(JVM执行指令的工作台)
// 技术栈:Java 11
public class ThreadStackDemo {
public static void main(String[] args) {
// 创建一个新线程(新工人)
Thread worker = new Thread(() -> {
int taskId = 1; // 栈帧中的局部变量
System.out.println("处理任务:" + taskId);
});
worker.start(); // JVM在这里分配线程栈
}
}
// 注释:lambda表达式中的代码会在新线程栈中执行
二、线程栈的存储秘密
线程栈的大小可以通过-Xss参数调整(默认1MB),但这里有个隐藏陷阱:栈深度太大会导致StackOverflowError。比如递归调用没写好:
// 技术栈:Java 11
public class StackCrash {
static void recursiveCall(int count) {
System.out.println("深度:" + count);
recursiveCall(count + 1); // 无限递归,直到栈空间耗尽
}
public static void main(String[] args) {
recursiveCall(1); // 最终抛出StackOverflowError
}
}
// 注释:每个方法调用都会在栈中压入新帧
关联技术:通过jstack命令可以查看线程栈状态,这是排查死锁的利器:
jstack <pid> # 打印所有线程的调用栈
三、并发编程的基石
线程栈的存在让并发成为可能。看个经典的生产者-消费者例子:
// 技术栈:Java 11
import java.util.LinkedList;
import java.util.Queue;
public class ProducerConsumer {
private static final int MAX_SIZE = 5;
private final Queue<Integer> queue = new LinkedList<>();
synchronized void produce() throws InterruptedException {
while (queue.size() == MAX_SIZE) {
wait(); // 释放锁,当前线程进入等待队列
}
queue.add(1);
notifyAll(); // 唤醒其他线程
}
synchronized void consume() throws InterruptedException {
while (queue.isEmpty()) {
wait();
}
queue.poll();
notifyAll();
}
}
// 注释:wait()会让线程暂时退出竞争,进入WAITING状态
注意事项:
synchronized锁的是对象头中的Mark Word- 每个等待的线程都会被封装成
Monitor对象
四、现代并发工具实战
Java后来引入了更强大的java.util.concurrent包。比如用ThreadPoolExecutor管理线程:
// 技术栈:Java 11
import java.util.concurrent.*;
public class ThreadPoolDemo {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // 核心线程数
5, // 最大线程数
60, TimeUnit.SECONDS, // 空闲线程存活时间
new ArrayBlockingQueue<>(10) // 任务队列
);
executor.execute(() -> {
System.out.println("任务在线程:" + Thread.currentThread().getName());
});
executor.shutdown(); // 优雅关闭
}
}
// 注释:线程池中的工作线程会复用线程栈空间
五、技术选型与避坑指南
应用场景对比:
| 技术 | 适用场景 | 缺点 |
|---------------|-------------------------|-----------------------|
| synchronized | 简单的互斥控制 | 性能较差 |
| ReentrantLock | 需要尝试获取锁的场景 | 必须手动释放 |
| ThreadPool | 大量短期异步任务 | 参数配置复杂 |
终极建议:
- 避免在锁内调用外部方法(容易造成死锁)
- 线程池队列建议使用有界队列
- 推荐使用
ThreadLocal保存线程私有数据
// 技术栈:Java 11
public class ThreadLocalSafe {
private static final ThreadLocal<SimpleDateFormat> formatter =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public static void main(String[] args) {
// 每个线程获取自己独立的SimpleDateFormat实例
System.out.println(formatter.get().format(new Date()));
}
}
// 注释:ThreadLocal内部使用Thread.currentThread()作为key
评论