1. 线程的基本概念与核心价值
在Java编程中,线程(Thread)作为程序执行的最小单元,其重要性不亚于面向对象编程中的类和对象。想象一下,线程就像餐厅里的服务员——单线程程序如同只有一位服务员的餐厅,所有顾客必须排队等待;而多线程程序则像拥有多个服务员的餐厅,可以同时服务多桌客人,显著提升整体效率。
线程的本质是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。每个Java程序启动时,JVM都会为我们创建一个主线程(main thread),这也是为什么我们能在main方法中直接编写顺序执行的代码。但真正的威力在于我们可以创建多个线程,让它们并发执行不同的任务。
关键理解:线程与进程的核心区别在于资源占用。同一进程下的多个线程共享堆内存和方法区,但每个线程拥有独立的程序计数器、虚拟机栈和本地方法栈。这种"共享+独立"的设计,既减少了内存开销,又保证了执行流的独立性。
2. Java线程的生命周期与状态转换
2.1 线程的六种基础状态
Java线程的生命周期被精确定义为六种状态(java.lang.Thread.State枚举):
- NEW(新建):线程对象刚被创建,但尚未调用start()方法
- RUNNABLE(可运行):调用start()后进入此状态,包括正在运行和准备就绪两种情况
- BLOCKED(阻塞):线程等待获取监视器锁(如synchronized块)
- WAITING(无限等待):调用Object.wait()、Thread.join()等方法进入
- TIMED_WAITING(限时等待):带有超时参数的等待状态(如sleep(1000))
- TERMINATED(终止):线程执行完毕或异常退出
java复制// 典型的状态转换示例
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000); // TIMED_WAITING
synchronized(lock) { // BLOCKED(如果锁被占用)
lock.wait(); // WAITING
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(thread.getState()); // NEW
thread.start();
System.out.println(thread.getState()); // RUNNABLE
2.2 状态转换的触发条件
状态转换的完整路径如下:
code复制NEW → start() → RUNNABLE
RUNNABLE → 获取锁失败 → BLOCKED
RUNNABLE → wait()/join() → WAITING
RUNNABLE → sleep(n)/wait(n)/join(n) → TIMED_WAITING
WAITING/TIMED_WAITING → notify()/notifyAll()/超时 → RUNNABLE
BLOCKED → 获取锁成功 → RUNNABLE
RUNNABLE → 执行结束/异常 → TERMINATED
实战经验:通过jstack工具查看线程状态是排查死锁、线程泄漏问题的关键。当大量线程处于BLOCKED或WAITING状态时,往往预示着性能瓶颈或设计问题。
3. 线程的创建与启动方式
3.1 继承Thread类(不推荐)
最基础的创建方式,但存在单继承限制:
java复制class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running: " + getName());
}
}
// 使用示例
MyThread thread = new MyThread();
thread.start(); // 注意:必须调用start()而非run()
3.2 实现Runnable接口(推荐)
更灵活的方案,避免继承局限:
java复制class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable running: " +
Thread.currentThread().getName());
}
}
// 使用示例
Thread thread = new Thread(new MyRunnable(), "MyThread-1");
thread.start();
3.3 使用Callable和Future(带返回值)
需要返回结果或抛出异常时使用:
java复制Callable<Integer> task = () -> {
TimeUnit.SECONDS.sleep(2);
return 42;
};
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(task);
System.out.println("Result: " + future.get()); // 阻塞直到获取结果
executor.shutdown();
3.4 Lambda表达式简化(Java8+)
利用函数式接口简化代码:
java复制new Thread(() -> {
System.out.println("Lambda thread: " +
Thread.currentThread().getName());
}).start();
避坑指南:直接调用run()方法是常见错误,这会导致代码在当前线程同步执行,而非启动新线程。正确的启动方式必须通过start()方法触发JVM创建新线程。
4. 线程调度与优先级机制
4.1 线程调度的底层原理
Java线程调度依赖于操作系统的原生线程实现,采用抢占式调度模型。关键点包括:
- 时间片轮转:每个线程获得CPU时间片执行(通常10-100ms)
- 优先级提示:通过setPriority()设置(1-10,默认5),但不同OS实现差异大
- yield()方法:提示调度器当前线程可让出CPU(实际效果不可靠)
java复制Thread highPriority = new Thread(() -> {
for (int i = 0; i < 100; i++)
System.out.print("高");
});
highPriority.setPriority(Thread.MAX_PRIORITY);
Thread lowPriority = new Thread(() -> {
for (int i = 0; i < 100; i++)
System.out.print("低");
});
lowPriority.setPriority(Thread.MIN_PRIORITY);
lowPriority.start();
highPriority.start();
4.2 守护线程(Daemon Thread)
具有以下特性的特殊线程:
- 不会阻止JVM退出(当只剩守护线程时JVM自动终止)
- 通常用于后台支持任务(如GC线程)
- 通过setDaemon(true)设置,必须在start()前调用
java复制Thread daemon = new Thread(() -> {
while (true) {
System.out.println("Daemon working...");
try { Thread.sleep(1000); }
catch (InterruptedException e) { break; }
}
});
daemon.setDaemon(true);
daemon.start();
性能提示:在Linux系统下,Java线程直接对应内核线程(1:1模型),创建成本较高。而Windows采用混合模型,线程创建相对轻量。这也是为什么在Linux环境下更推荐使用线程池。
5. 线程安全与同步机制
5.1 竞态条件与原子性
典型的多线程问题示例:
java复制class Counter {
private int count = 0;
public void increment() { count++; } // 非原子操作
public int getCount() { return count; }
}
// 测试代码
Counter counter = new Counter();
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
executor.submit(counter::increment);
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
System.out.println(counter.getCount()); // 可能小于1000
5.2 同步解决方案对比
5.2.1 synchronized关键字
java复制// 同步方法
public synchronized void increment() { count++; }
// 同步块
public void increment() {
synchronized(this) { // 锁对象
count++;
}
}
5.2.2 volatile变量
适用于单一变量的可见性保证:
java复制private volatile boolean running = true;
public void stop() { running = false; }
5.2.3 原子类(推荐)
java复制private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // CAS操作
}
5.2.4 Lock接口
更灵活的锁机制:
java复制private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock(); // 必须手动释放
}
}
5.3 死锁分析与预防
典型死锁场景:
java复制// 线程1
synchronized(lockA) {
Thread.sleep(100);
synchronized(lockB) { ... }
}
// 线程2
synchronized(lockB) {
Thread.sleep(100);
synchronized(lockA) { ... }
}
预防策略:
- 固定锁获取顺序(如按hash值排序)
- 使用tryLock()设置超时
- 通过jstack检测死锁(输出中包含"Found one Java-level deadlock")
高级技巧:使用ThreadMXBean可以编程式检测死锁:
java复制ThreadMXBean bean = ManagementFactory.getThreadMXBean();
long[] threadIds = bean.findDeadlockedThreads();
if (threadIds != null) {
System.err.println("检测到死锁!");
}
6. 线程间通信与协作
6.1 wait/notify机制
生产者-消费者模式经典实现:
java复制class Buffer {
private Queue<Integer> queue = new LinkedList<>();
private int capacity;
public Buffer(int capacity) { this.capacity = capacity; }
public synchronized void produce(int item) throws InterruptedException {
while (queue.size() == capacity) {
wait(); // 缓冲区满时等待
}
queue.add(item);
notifyAll(); // 通知消费者
}
public synchronized int consume() throws InterruptedException {
while (queue.isEmpty()) {
wait(); // 缓冲区空时等待
}
int item = queue.poll();
notifyAll(); // 通知生产者
return item;
}
}
6.2 Condition条件变量
更精细的等待/通知控制:
java复制private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
public void produce(int item) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) {
notFull.await(); // 专门等待非满条件
}
queue.add(item);
notEmpty.signal(); // 只唤醒消费者
} finally {
lock.unlock();
}
}
6.3 阻塞队列实现
更高级的线程安全集合:
java复制BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
// 生产者
queue.put(item); // 自动阻塞
// 消费者
int item = queue.take(); // 自动阻塞
性能对比:synchronized在低竞争场景下性能更好,而ReentrantLock在高竞争场景下表现更优,且提供更丰富的功能(如公平锁、条件变量等)。
7. 线程池的最佳实践
7.1 Executor框架体系
java复制ExecutorService singleThread = Executors.newSingleThreadExecutor();
ExecutorService fixedPool = Executors.newFixedThreadPool(4);
ExecutorService cachedPool = Executors.newCachedThreadPool();
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
// 定时任务
scheduler.scheduleAtFixedRate(() ->
System.out.println("定时执行"), 1, 1, TimeUnit.SECONDS);
7.2 自定义线程池参数
java复制ThreadPoolExecutor executor = new ThreadPoolExecutor(
4, // 核心线程数
8, // 最大线程数
60, // 空闲线程存活时间
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), // 工作队列
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
7.3 四种拒绝策略对比
- AbortPolicy(默认):抛出RejectedExecutionException
- CallerRunsPolicy:由提交任务的线程直接执行
- DiscardPolicy:静默丢弃新任务
- DiscardOldestPolicy:丢弃队列中最老的任务
配置建议:根据阿姆达尔定律,线程数并非越多越好。CPU密集型任务建议设置为CPU核数+1,IO密集型任务可设置为CPU核数*2。可通过Runtime.getRuntime().availableProcessors()获取CPU核心数。
8. Java内存模型与线程安全设计
8.1 JMM核心概念

(注:实际使用时需替换为合法图片链接)
- 主内存:所有线程共享的内存区域
- 工作内存:每个线程私有的内存副本
- happens-before原则:定义跨线程操作的内存可见性规则
8.2 final的内存语义
java复制class FinalExample {
final int x;
int y;
public FinalExample() {
x = 42; // final字段必须在构造函数完成前初始化
y = 1;
}
void reader() {
if (x == 42) {
System.out.println(y); // 保证看到y=1
}
}
}
8.3 ThreadLocal的使用场景
java复制private static final ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public String formatDate(Date date) {
return dateFormat.get().format(date); // 每个线程独立实例
}
避坑提醒:ThreadLocal使用后必须remove(),否则在线程池场景下会导致内存泄漏。因为线程池会复用线程,使ThreadLocal的强引用无法被GC回收。
9. 现代并发工具类应用
9.1 CountDownLatch(倒计时门闩)
java复制CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
Thread.sleep((long)(Math.random() * 1000));
System.out.println(Thread.currentThread().getName() + " 完成");
latch.countDown();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
latch.await(); // 阻塞直到计数器归零
System.out.println("所有任务完成");
9.2 CyclicBarrier(循环栅栏)
java复制CyclicBarrier barrier = new CyclicBarrier(4,
() -> System.out.println("所有线程到达屏障点"));
for (int i = 0; i < 4; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 准备");
Thread.sleep((long)(Math.random() * 2000));
barrier.await(); // 等待其他线程
System.out.println(Thread.currentThread().getName() + " 继续");
} catch (Exception e) {
Thread.currentThread().interrupt();
}
}).start();
}
9.3 CompletableFuture(异步编程)
java复制CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(500); }
catch (InterruptedException e) { throw new RuntimeException(e); }
return "Hello";
}).thenApplyAsync(s -> s + " World")
.thenAccept(System.out::println)
.join(); // 等待完成
10. 线程性能调优实战
10.1 上下文切换开销测试
java复制long start = System.currentTimeMillis();
AtomicInteger counter = new AtomicInteger();
ExecutorService executor = Executors.newFixedThreadPool(100);
for (int i = 0; i < 100_000; i++) {
executor.submit(counter::incrementAndGet);
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
System.out.printf("计数器: %d, 耗时: %dms%n",
counter.get(), System.currentTimeMillis() - start);
10.2 锁优化技巧
- 减小锁粒度:从方法级锁改为代码块锁
- 锁分离:读写锁分离(ReentrantReadWriteLock)
- 无锁编程:使用原子类或CAS操作
- 偏向锁/自旋锁:JVM自动优化(-XX:+UseBiasedLocking)
10.3 线程转储分析
bash复制# 生成线程转储
jstack <pid> > thread_dump.txt
# 查找CPU高的线程
top -H -p <pid>
printf "%x\n" <thread_id> # 转换为16进制
生产经验:线上系统建议配置-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump.hprof,在OOM时自动生成堆转储文件,结合MAT工具分析线程问题。
