- 为什么需要多线程? - 多线程的价值
- Java 线程的两种创建方式 -
Thread和Runnable - 线程的生命周期 - 从创建到销毁的整个过程
- 线程的核心方法 -
start(),run(),sleep(),join()等 - 线程同步 - 解决多线程并发问题(
synchronized,Lock) - 线程间通信 -
wait(),notify(),notifyAll() - 线程池 -
ExecutorService- 高效管理线程 - 现代异步编程 -
CompletableFuture - 最佳实践与注意事项
为什么需要多线程?
多线程是为了提高程序的运行效率和响应速度。

- 提高 CPU 利用率:在单核 CPU 中,多线程通过快速切换(时间片轮转)来模拟“执行,当一个线程因 I/O(如读写文件、网络请求)阻塞时,CPU 可以立即切换到其他线程执行,避免了 CPU 闲置。
- 提升程序响应速度:对于图形界面(GUI)应用,可以将耗时操作(如下载文件、复杂计算)放在后台线程执行,避免主线程(UI线程)被阻塞,从而保持界面的流畅和响应。
- 简化程序模型:对于某些任务(如服务器处理多个客户端请求),使用多线程可以将每个任务的处理逻辑封装在一个独立的线程中,使程序结构更清晰。
Java 线程的两种创建方式
在 Java 中,创建线程主要有两种方式,并且推荐使用第二种。
继承 Thread 类
这是最简单直接的方式,你创建一个新类,继承自 java.lang.Thread,并重写其 run() 方法。
// 1. 继承 Thread 类
class MyThread extends Thread {
@Override
public void run() {
// 线程要执行的代码
for (int i = 0; i < 5; i++) {
System.out.println("MyThread is running: " + i);
try {
Thread.sleep(500); // 暂停 500 毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
// 2. 创建线程对象
MyThread thread = new MyThread();
// 3. 启动线程 (注意:不是调用 run() 方法)
thread.start();
// 主线程的代码
for (int i = 0; i < 5; i++) {
System.out.println("Main thread is running: " + i);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
缺点:Java 是单继承的,如果继承了 Thread 类,就无法再继承其他类了,灵活性较差。
实现 Runnable 接口(推荐)
这种方式更加灵活,符合“面向接口编程”的思想,你只需要创建一个类实现 Runnable 接口,并实现其 run() 方法,然后将这个 Runnable 实例传给 Thread 类。

// 1. 实现 Runnable 接口
class MyRunnable implements Runnable {
@Override
public void run() {
// 线程要执行的代码
for (int i = 0; i < 5; i++) {
System.out.println("MyRunnable is running: " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
// 2. 创建 Runnable 实例
MyRunnable runnable = new MyRunnable();
// 3. 创建 Thread 对象,并将 Runnable 实例传入
Thread thread = new Thread(runnable);
// 4. 启动线程
thread.start();
// 主线程的代码
for (int i = 0; i < 5; i++) {
System.out.println("Main thread is running: " + i);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
优点:
- 避免了单继承的限制。
- 将线程任务(
run()方法的代码)和线程创建(Thread对象)解耦,代码结构更清晰。
线程的生命周期
一个线程从创建到销毁,会经历几个不同的状态,可以通过 Thread.getState() 方法查看。
- NEW (新建):线程被创建但尚未启动。
thread.start()之前的状态。 - RUNNABLE (可运行):线程已经启动,可能正在运行,也可能正在等待 CPU 时间片,在 Java 中,就绪状态和运行状态统称为
RUNNABLE。 - BLOCKED (阻塞):线程因为等待监视器锁(即
synchronized代码块或方法)而进入阻塞状态,它不会执行,直到它获取到锁。 - WAITING (等待):线程因为调用
wait(),join()或LockSupport.park()而进入无限等待状态,它需要其他线程显式地唤醒(如notify()或notifyAll())。 - TIMED_WAITING (计时等待):和
WAITING类似,但它是在指定的时间内等待。sleep(long millis),wait(long timeout),join(long millis)。 - TERMINATED (终止):线程已经执行完毕或被中断,生命周期结束。
状态转换图:
NEW -> start() -> RUNNABLE -> (获取到 CPU) -> 运行中
RUNNABLE -> (失去 CPU/调用 sleep/wait) -> BLOCKED/WAITING/TIMED_WAITING
BLOCKED/WAITING/TIMED_WAITING -> (获取锁/被唤醒/sleep时间到) -> RUNNABLE
RUNNABLE -> (执行完毕) -> TERMINATED
线程的核心方法
| 方法 | 作用 | 备注 |
|---|---|---|
start() |
启动线程,JVM 会调用该线程的 run() 方法。 |
只能调用一次,直接调用 run() 只是在当前线程执行 run() 中的代码,不会启动新线程。 |
run() |
线程执行的主体,包含要执行的任务代码。 | 通常需要重写。 |
sleep(long millis) |
让当前线程“休眠”指定的毫秒数,进入 TIMED_WAITING 状态。 |
不会释放锁。 |
join() |
等待该线程终止。 | 如果在主线程中调用 thread.join(),主线程会阻塞,直到 thread 线程执行完毕。 |
yield() |
提示当前线程已经完成了一次工作,可以“礼让”给同等优先级的其他线程。 | 不保证一定会礼让,只是建议。 |
interrupt() |
中断线程。 | 它不会立即停止线程,而是设置一个中断状态,线程可以通过 isInterrupted() 或 InterruptedException 来感知中断。 |
isAlive() |
测试线程是否处于活动状态(RUNNABLE, BLOCKED, WAITING, TIMED_WAITING)。 |
线程同步
当多个线程同时访问和修改共享资源(如一个变量、一个对象)时,可能会导致数据不一致的问题,这就是线程安全问题。

问题示例:银行取款
class Account {
private int balance = 1000;
public void withdraw(int amount) {
if (balance >= amount) {
try {
// 模拟网络延迟等耗时操作
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
System.out.println(Thread.currentThread().getName() + " 取款成功,余额: " + balance);
} else {
System.out.println(Thread.currentThread().getName() + " 余额不足,余额: " + balance);
}
}
}
public class UnsafeDemo {
public static void main(String[] args) {
Account account = new Account();
Thread t1 = new Thread(() -> account.withdraw(500), "线程A");
Thread t2 = new Thread(() -> account.withdraw(500), "线程B");
t1.start();
t2.start();
}
}
可能输出:
线程A 取款成功,余额: 500
线程B 取款成功,余额: 0 // 错误!
原因:if (balance >= amount) 和 balance -= amount 这两个操作不是一个原子操作,在 t1 检查完余额后,t2 也检查了,然后两个线程都认为余额足够,导致余额被减了两次。
解决方案一:synchronized 关键字
synchronized 可以保证在同一时间,只有一个线程能进入被 synchronized 修饰的代码块或方法,从而保证了原子性。
-
同步代码块:更灵活,可以指定锁对象。
public void withdraw(int amount) { synchronized (this) { // 锁对象是 Account 实例 if (balance >= amount) { // ... } } } -
同步方法:锁对象默认是
this(对于实例方法)或Class对象(对于静态方法)。public synchronized void withdraw(int amount) { // ... }使用
synchronized后,上面的取款问题就能得到解决。
解决方案二:java.util.concurrent.locks.Lock 接口
Lock 提供了比 synchronized 更强大的功能,例如尝试获取锁、可中断地获取锁、超时获取锁等。
最常用的实现是 ReentrantLock。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class SafeAccount {
private int balance = 1000;
private final Lock lock = new ReentrantLock(); // 创建锁
public void withdraw(int amount) {
lock.lock(); // 加锁
try {
if (balance >= amount) {
Thread.sleep(1);
balance -= amount;
System.out.println(Thread.currentThread().getName() + " 取款成功,余额: " + balance);
} else {
System.out.println(Thread.currentThread().getName() + " 余额不足,余额: " + balance);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 释放锁,必须放在 finally 块中
}
}
}
Lock 比 synchronized 更灵活,但使用时必须手动加锁和释放锁,否则会导致死锁。
线程间通信
当多个线程需要协作完成任务时,就需要线程间通信,经典的生产者-消费者模型就是例子。
Object 类提供了三个方法来实现等待/通知机制:
wait():让当前线程等待,直到其他线程调用notify()或notifyAll(),调用此方法的线程必须拥有该对象的锁。notify():唤醒一个正在等待该对象锁的线程(是随机的)。notifyAll():唤醒所有正在等待该对象锁的线程。
示例:生产者生产商品,消费者消费商品。
class Product {
private String name;
private boolean isEmpty = true; // true表示为空
// 消费者调用
public synchronized void consume() throws InterruptedException {
if (isEmpty) {
System.out.println("消费者:商品为空,等待生产...");
wait(); // 等待,并释放锁
}
System.out.println("消费者:消费了 " + name);
isEmpty = true;
notifyAll(); // 唤醒生产者
}
// 生产者调用
public synchronized void produce(String name) throws InterruptedException {
if (!isEmpty) {
System.out.println("生产者:商品已满,等待消费...");
wait(); // 等待,并释放锁
}
this.name = name;
System.out.println("生产者:生产了 " + name);
isEmpty = false;
notifyAll(); // 唤醒消费者
}
}
注意:wait(), notify(), notifyAll() 必须在同步代码块或同步方法中调用,并且操作的是同一个锁对象。
线程池
频繁地创建和销毁线程是非常消耗资源的,线程池就是为了解决这个问题而生的,它会预先创建一组线程,当有任务需要执行时,就从池中取出一个线程来执行,任务执行完毕后,线程不会销毁,而是返回池中等待下一次任务。
核心接口:ExecutorService
Java 提供了 java.util.concurrent.ExecutorService 接口来管理线程池。
创建线程池的方式:
-
Executors工厂类:简单易用,但不适合生产环境。Executors.newFixedThreadPool(int n): 创建一个固定大小的线程池。Executors.newCachedThreadPool(): 创建一个可缓存的线程池,大小不固定。Executors.newSingleThreadExecutor(): 创建一个单线程的线程池。
-
ThreadPoolExecutor类:功能最强大,是线程池的真正实现,推荐在生产环境中使用。
示例:使用 Executors
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 创建一个固定大小为 3 的线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 5; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行任务 " + taskId);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executor.shutdown(); // 等待所有任务完成后关闭
// executor.shutdownNow(); // 立即关闭,尝试停止正在执行的任务
}
}
使用 ThreadPoolExecutor 的推荐方式:
// 核心参数
int corePoolSize = 5; // 核心线程数
int maximumPoolSize = 10; // 最大线程数
long keepAliveTime = 60L; // 空闲线程存活时间
TimeUnit unit = TimeUnit.SECONDS; // 时间单位
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(100); // 工作队列
ThreadFactory threadFactory = Executors.defaultThreadFactory(); // 线程工厂
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy(); // 拒绝策略
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
threadFactory,
handler
);
现代异步编程:CompletableFuture
从 Java 8 开始,CompletableFuture 提供了一种更强大、更灵活的方式来处理异步编程,它结合了 Future 的功能和函数式编程,可以方便地进行链式调用和组合多个异步任务。
示例:异步执行任务并处理结果
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1. 异步执行一个任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello, CompletableFuture!";
});
// 2. 当任务完成时,执行这个回调
future.thenAccept(result -> {
System.out.println("收到结果: " + result);
});
// 3. 主线程可以继续做其他事情
System.out.println("主线程继续执行...");
// 为了看到结果,让主线程等待一下
future.get(); // 阻塞,直到 future 完成
}
}
CompletableFuture 提供了丰富的 API,如 thenApply, thenCompose, thenCombine, exceptionally 等,使得复杂的异步流程处理变得非常优雅。
最佳实践与注意事项
- 避免过度使用线程:不是所有场景都需要多线程,对于计算密集型任务,在单核 CPU 上,多线程反而会因为线程切换的开销而降低性能。
- 避免
synchronized滥用:synchronized会影响并发性能,尽量使用java.util.concurrent包下的工具类,如ConcurrentHashMap,CopyOnWriteArrayList,AtomicInteger等,它们是为并发场景设计的。 - 注意死锁:当两个或多个线程互相等待对方释放锁时,就会发生死锁,避免在持有锁时调用其他可能阻塞的方法(如
IO操作),尽量以固定的顺序获取多个锁。 - 注意资源泄漏:使用
try-finally或try-with-resources确保锁、文件、数据库连接等资源被正确释放。 - 优先使用线程池:不要手动创建和管理大量线程。
- 优先使用
CompletableFuture或Stream进行异步和并行处理:它们是现代 Java 中处理并发任务的更高级、更简洁的方式。
希望这份详细的指南能帮助你全面理解 Java 多线程!
