Java 多线程入门
摘要
随着多核处理器普及,Java 并发编程已是后端与桌面应用中不可或缺的一部分。本文从概念、常见写法到常见问题与最佳实践,对 Java 多线程进行系统梳理,并补充了线程池、Callable/Future、并发工具类与实用建议。
目录
-
引言
-
线程是什么(Java 角度)
-
常见创建线程的方式
-
更现代的并发:线程池与异步任务
-
同步、可见性与线程安全
-
常见并发工具与用法速查
-
常见陷阱与调试技巧
-
实际开发中的最佳实践
-
总结与参考标签
1 引言
多核 CPU 把并行计算带到了桌面与服务器端。单靠顺序执行无法充分利用硬件并行能力,Java 提供了线程与丰富的并发库,使我们可以把工作拆成多个并发任务来提高吞吐与响应能力。但并发也带来复杂性:共享状态、竞态、死锁、内存可见性等问题必须谨慎处理。
2 线程是什么(Java 角度)
在 Java 中,线程是程序执行的最小单元。JVM 的线程通常映射到操作系统线程(平台相关),由操作系统负责调度。线程有生命周期:NEW → RUNNABLE → BLOCKED/WAITING/TIMED_WAITING → TERMINATED。理解这些状态有助于定位阻塞与死锁问题。
3 常见创建线程的方式
3.1 继承 Thread
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello from Thread");
}
}
public class Test {
public static void main(String[] args) {
Thread t = new MyThread();
t.start(); // 不能调用 run(),否则变成普通方法调用
}
}
3.2 实现 Runnable
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Hello from Runnable");
}
}
public class Test {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
}
3.3 Callable 与 Future(有返回值/可抛异常)
import java.util.concurrent.*;
Callable<Integer> task = () -> {
Thread.sleep(100);
return 42;
};
ExecutorService exec = Executors.newSingleThreadExecutor();
Future<Integer> f = exec.submit(task);
try {
Integer result = f.get(); // 阻塞等待结果
System.out.println(result);
} finally {
exec.shutdown();
}
4 更现代的并发:线程池与异步任务
直接 new Thread()通常不是好选择,推荐使用 ExecutorService 和线程池来管理线程生命周期与复用资源。
ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(() -> {
// 业务逻辑
});
pool.shutdown();
Java 还提供 CompletableFuture,便于写链式异步逻辑、组合多个异步任务:
CompletableFuture.supplyAsync(() -> compute())
.thenApply(result -> transform(result))
.exceptionally(ex -> { /* 处理异常 */ return null; });
5 同步、可见性与线程安全
多线程主要问题来自共享可变状态。要保证线程安全,有两类工作:互斥与可见性。
5.1 互斥(Mutual Exclusion)
-
synchronized:方法或代码块级别的锁,简单易用。 -
ReentrantLock:更灵活(可中断锁、可尝试获取、可设超时)。
示例 synchronized:
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int get() {
return count;
}
}
5.2 可见性
-
volatile:保证写入对其它线程可见,适合标志位或单个变量的可见性,但不能做复合操作(如count++)。 -
原子类(
AtomicInteger等):提供原子操作,避免使用锁的场景下仍能保证一致性。
示例 AtomicInteger:
import java.util.concurrent.atomic.AtomicInteger;
public class SafeCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public int get() {
return count.get();
}
}
5.3 内存模型提示(JMM)
Java 内存模型(JMM)定义了操作在多线程间的可见性和重排序规则。synchronized、volatile、Lock 和并发集合都涉及内存屏障(happens-before)语义,理解这些语义有助于避免难以复现的 bug。
6 常见并发工具与用法速查
-
ExecutorService/ThreadPoolExecutor:线程池管理。 -
ScheduledExecutorService:定时任务。 -
CountDownLatch:等待一组事件完成。 -
CyclicBarrier:多个线程相互等待到达某点。 -
Semaphore:限制同时访问资源的线程数量。 -
ConcurrentHashMap:高性能并发映射。 -
BlockingQueue(如LinkedBlockingQueue):生产者-消费者模式。 -
ForkJoinPool:适合分治(Fork/Join)并行计算。 -
CompletableFuture:异步编排与组合。
7 常见陷阱与调试技巧
-
不要吞掉中断:被阻塞的方法(如
sleep、wait、join、blocking queue)抛出InterruptedException,应正确响应(通常是恢复中断或退出线程)。 -
避免死锁:保证加锁顺序一致、尽量减少持有锁的时间、使用
tryLock和超时策略。 -
慎用
volatile:它保证可见性但不保证复合操作的原子性。 -
避免忙等待(busy-waiting):使用阻塞队列或条件变量替代循环检查。
-
使用线程dump(jstack)和日志:定位死锁或线程阻塞时非常有用。
-
并发问题往往是时间相关:用压力测试、线程模糊测试(fuzzing)或并发单元测试工具检查。
8 实际开发中的最佳实践
-
优先使用高层并发工具(线程池、BlockingQueue、Concurrent Collections、CompletableFuture),避免直接管理线程。
-
尽量保持数据不可变(immutable 对象),这样能减少同步需求。
-
把共享可变状态局部化(ThreadLocal)或通过消息传递/队列来隔离。
-
合理配置线程池大小:CPU 密集型任务线程数 ≈ CPU 核数 + 1;IO 密集型可更大,但要结合实际监控调整。
-
处理中断与超时:为阻塞操作设置超时,处理 InterruptedException,避免线程无限挂起。
-
记录线程上下文(如采用 MDC 输出日志),排查问题时更方便。
-
测试并发:使用压力测试、单元并发测试库(如 JUnit + Executors)以及静态分析工具。
9 总结
Java 多线程既为程序性能与响应性带来提升,也引入了复杂性。掌握线程创建、线程池与标准并发工具,理解同步与内存可见性原则,并遵循工程实践(优先高层工具、合理配置线程池、避免共享可变状态),可以把多线程风险降到最低。实践中应结合日志、线程 dump 与测试来稳步推进并发代码的可靠性。
更多推荐



所有评论(0)