Java 多线程详解:从基础到实践
多线程是 Java 编程中处理并发任务的核心机制,它允许程序同时执行多个任务,显著提升资源利用率和程序响应速度。本文将从线程的基本概念出发,系统讲解 Java 多线程的实现方式、核心原理及实践要点。
一、线程与进程:并发的基础概念
- 进程:操作系统分配资源的基本单位,每个进程拥有独立的内存空间、文件描述符等资源,进程间相互隔离。例如,打开的浏览器、IDE 都是独立进程。
- 线程:进程内的执行单元,共享进程的资源(内存、文件等),是 CPU 调度的基本单位。一个进程可以包含多个线程,线程间切换的开销远小于进程。
为什么需要多线程?
- 提高 CPU 利用率:当一个线程因 IO 操作(如读写文件、网络请求)阻塞时,其他线程可继续执行;
- 提升程序响应速度:例如 GUI 程序中,后台线程处理数据,主线程保持界面响应;
- 简化复杂任务:将大任务拆分为多个并行子任务,缩短整体执行时间。
二、Java 中创建线程的三种方式
1. 继承 Thread 类
Thread 类是 Java 线程的基础类,通过继承它并重写run()方法定义线程任务:
class MyThread extends Thread {
@Override
public void run() {
// 线程执行的任务
for (int i = 0; i < 5; i++) {
System.out.println("子线程执行:" + i);
}
}
}
// 使用方式
public class Test {
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start(); // 启动线程(底层调用run())
// 主线程任务
for (int i = 0; i < 5; i++) {
System.out.println("主线程执行:" + i);
}
}
}
注意:必须调用start()而非直接调用run(),start()会触发 JVM 创建新线程并执行run(),直接调用run()只是普通方法调用,不会启动新线程。
2. 实现 Runnable 接口
Runnable 是函数式接口(仅含run()方法),通过实现它定义任务,再传入 Thread 类:
class MyRunnable implements Runnable {
@Override
public void run() {
// 线程任务
for (int i = 0; i < 5; i++) {
System.out.println("子线程执行:" + i);
}
}
}
// 使用方式
public class Test {
public static void main(String[] args) {
Runnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
优势:避免单继承限制(一个类可同时实现 Runnable 和其他接口),更适合多线程共享任务逻辑的场景。
3. 实现 Callable 接口(带返回值)
Callable 接口与 Runnable 类似,但call()方法可返回结果且能抛出异常,需配合 FutureTask 使用:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += i;
}
return sum; // 返回计算结果
}
}
// 使用方式
public class Test {
public static void main(String[] args) throws Exception {
Callable<Integer> callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
// 获取子线程返回值(会阻塞主线程直到结果返回)
int result = futureTask.get();
System.out.println("计算结果:" + result); // 输出55
}
}
三、线程的生命周期与状态转换
Java 线程有 6 种状态(定义在Thread.State枚举中),状态转换是多线程控制的核心:
- 新建(New):线程对象创建后,未调用
start()前的状态。 - 就绪(Runnable):调用
start()后,线程进入就绪队列等待 CPU 调度(此时可能正在运行,也可能等待)。 - 运行(Running):CPU 调度线程执行
run()方法的状态。 - 阻塞(Blocked):线程因竞争同步锁失败而暂停(如进入
synchronized代码块未获取锁)。 - 等待(Waiting):线程调用
wait()、join()等方法后,需其他线程唤醒才能继续(无超时时间)。 - 超时等待(Timed Waiting):线程调用
sleep(long)、wait(long)等方法,等待指定时间后自动唤醒。 - 终止(Terminated):
run()方法执行完毕或线程被中断,线程生命周期结束。
核心转换逻辑:新建 → 就绪(start())→ 运行(CPU 调度)→ 终止(任务完成)运行 → 阻塞(竞争锁失败)→ 就绪(获取锁)运行 → 等待 / 超时等待(调用相关方法)→ 就绪(被唤醒 / 超时)
四、线程控制的核心方法
| 方法 | 作用 | 注意事项 |
|---|---|---|
start() | 启动线程,进入就绪状态 | 不可重复调用,否则抛IllegalThreadStateException |
run() | 线程执行的任务逻辑 | 无需手动调用,由start()触发 |
sleep(long ms) | 让线程休眠指定毫秒,释放 CPU 资源 | 不会释放同步锁,休眠期间可被interrupt()中断 |
wait()/notify() | 线程间通信,释放同步锁等待 / 唤醒 | 必须在synchronized代码块中调用 |
join() | 让当前线程等待目标线程执行完毕 | 可指定超时时间,避免无限等待 |
interrupt() | 中断线程(设置中断标志,非强制终止) | 需线程主动检测isInterrupted()响应中断 |
yield() | 礼让线程,让当前线程重新进入就绪状态 | 仅为建议,CPU 可能忽略此请求 |
五、线程安全与同步机制
当多个线程共享资源时,可能出现数据不一致(如并发修改同一变量),需通过同步机制保证线程安全:
1. synchronized 关键字
- 作用:保证同一时刻只有一个线程执行同步代码块 / 方法,实现互斥访问。
- 使用方式:
// 同步方法 public synchronized void method() { ... } // 同步代码块(锁对象可为任意对象,推荐用当前对象this或类对象) public void method() { synchronized (this) { ... } } - 原理:基于对象内置锁(monitor)实现,进入同步块获取锁,退出释放锁。
-
2. Lock 接口(JDK 1.5+)
比
synchronized更灵活,支持尝试获取锁、超时释放等功能,常用实现类ReentrantLock: -
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Test { private Lock lock = new ReentrantLock(); public void method() { lock.lock(); // 获取锁 try { // 临界区代码 } finally { lock.unlock(); // 必须在finally中释放锁,避免死锁 } } }
3. 线程安全的容器
JDK 提供了线程安全的集合类,如Vector、HashTable,或通过Collections.synchronizedXXX()包装普通集合。更高效的并发容器(如ConcurrentHashMap、CopyOnWriteArrayList)位于java.util.concurrent包,适合高并发场景。
六、线程池:高效管理线程资源
频繁创建 / 销毁线程会消耗大量资源,线程池通过复用线程提高性能,是企业开发的首选方案。
核心接口与类
Executor:线程池顶层接口;ExecutorService:扩展 Executor,提供线程池生命周期管理;ThreadPoolExecutor:线程池核心实现类;Executors:线程池工具类,提供快捷创建方法(如newFixedThreadPool、newCachedThreadPool)。
基本使用
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) {
// 创建固定大小的线程池(3个线程)
ExecutorService pool = Executors.newFixedThreadPool(3);
// 提交任务
for (int i = 0; i < 5; i++) {
pool.submit(() -> {
System.out.println(Thread.currentThread().getName() + "执行任务");
});
}
// 关闭线程池(不再接受新任务,等待现有任务完成)
pool.shutdown();
}
}
七、多线程常见问题与解决方案
-
死锁:两个线程互相持有对方需要的锁,导致无限等待。解决:避免嵌套锁、按固定顺序获取锁、设置锁超时时间。
-
线程泄露:线程未正常终止,长期占用资源。解决:使用线程池管理线程,避免手动创建线程;确保线程能响应中断。
-
竞态条件:多线程并发修改共享变量导致数据错误。解决:使用
synchronized或Lock同步;使用原子类(如AtomicInteger)。
结语
多线程是 Java 并发编程的基础,掌握它需要理解线程生命周期、同步机制及资源管理。实际开发中,应优先使用线程池而非手动创建线程,结合java.util.concurrent包提供的工具类(如CountDownLatch、Semaphore)处理复杂并发场景。理解多线程的核心是平衡效率与安全,在保证数据一致性的前提下最大化利用系统资源。
更多推荐


所有评论(0)