Java多线程
Java多线程开发摘要:Java提供多种线程创建方式,包括继承Thread类、实现Runnable/Callable接口及Executor框架(推荐)。线程生命周期包含NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED六种状态,各状态通过特定方法相互转换。Executor框架提供多种线程池类型(Fixed/Cached/Scheduled等)
在Java中实现多线程确实能显著提升程序性能,特别是在多核处理器上处理并发任务时。
🧵 Java多线程开发全面解析
✨ 线程的创建方式
Java提供了多种灵活的方式来创建和管理线程,每种方法都有其适用的场景。
1. 继承Thread类
这是最基础的创建线程方式,通过继承Thread类并重写run()方法。
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行!");
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.start(); // 启动第一个线程
thread2.start(); // 启动第二个线程
}
}
优点:简单易用,适合快速实现多线程。
缺点:由于Java的单继承限制,如果类已经继承了其他类,则无法使用此方法。
2. 实现Runnable接口
这是更推荐的创建线程方式,它解决了单继承的限制,使代码更加灵活。
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行!");
}
}
public class RunnableExample {
public static void main(String[] args) {
Thread thread1 = new Thread(new MyRunnable());
Thread thread2 = new Thread(new MyRunnable());
thread1.start();
thread2.start();
}
}
优点:避免了单继承的局限性,任务与线程分离,代码结构更清晰。
缺点:需要手动管理线程的生命周期和资源。
3. 实现Callable接口 + FutureTask
当需要线程执行后返回结果时,Callable是更好的选择。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int a = 6;
int b = 9;
System.out.println("我是通过实现Callable接口创建的多线程");
return a + b;
}
}
class TestMyCallable {
public static void main(String[] args) throws Exception {
MyCallable myCallable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
Thread thread = new Thread(futureTask);
thread.start();
System.out.println("返回值为:" + futureTask.get()); // 获取返回值
}
}
优点:可以获得线程的返回结果,并且可以处理线程执行中的异常。
缺点:比Runnable复杂一些,Future的get方法是阻塞的。
4. 使用Executor框架(推荐)
Executor框架提供了更高级的线程管理机制,是工业级应用的首选。
import java.util.concurrent.*;
public class ExecutorExample {
public static void main(String[] args) {
// 创建固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(2);
// 提交Runnable任务
executorService.submit(() -> {
System.out.println("Task 1 is running");
});
// 提交Callable任务并获取Future
Future<Integer> future = executorService.submit(new MyCallable());
// 关闭线程池
executorService.shutdown();
}
}
Java提供了多种类型的线程池以适应不同场景:
- FixedThreadPool:固定线程数量,适用于任务数量固定的场景
- CachedThreadPool:按需创建线程,适合短生命周期任务
- ScheduledThreadPool:适用于需要定时或周期性执行任务的场景
- SingleThreadExecutor:单线程执行任务,保证顺序执行
优点:提供了更高层次的线程管理机制,简化了线程的使用和调度,减少了线程创建和销毁的开销。
缺点:学习曲线较陡,需要掌握更多的API和概念。
🔄 线程的生命周期与状态
Java线程在其生命周期中会经历以下几种状态,可以通过getState()方法查询当前状态:
线程状态详细说明
-
新建(NEW)
- 线程对象被创建但尚未启动的状态
- 示例:
Thread thread = new Thread();
-
可运行(RUNNABLE)
- 调用
start()方法后,线程进入就绪状态,等待CPU时间片 - 获得CPU时间片后,线程开始执行
run()方法
- 调用
-
阻塞(BLOCKED)
- 线程在等待监视器锁(synchronized锁)时进入此状态
- 发生在尝试进入同步方法或代码块时
-
等待(WAITING)
- 线程因调用以下方法之一而进入无限期等待:
Object.wait()Thread.join()LockSupport.park()
- 线程因调用以下方法之一而进入无限期等待:
-
超时等待(TIMED_WAITING)
- 与WAITING类似,但有时间限制:
Thread.sleep(long millis)Object.wait(long timeout)Thread.join(long millis)
- 与WAITING类似,但有时间限制:
-
终止(TERMINATED)
- 线程执行完毕(run()方法完成)或因异常退出后的状态
下面是Java线程状态流转的核心机制
🔄 状态流转详解
下面这个表格汇总了触发各种线程状态转换的具体方法和条件。
| 状态 | 触发方法/条件 | 说明与注意事项 |
|---|---|---|
| NEW → RUNNABLE | thread.start() |
线程对象创建后的初始状态。调用start()后,线程进入就绪队列,等待CPU调度。注意:直接调用run()方法只是在当前线程执行普通方法,不会启动新线程。 |
| RUNNABLE → BLOCKED | 尝试进入synchronized方法/块,但锁已被其他线程占用。或者进入阻塞IO |
此状态仅与synchronized锁竞争相关。在等待JUC(java.util.concurrent)包中的锁(如ReentrantLock)时,线程状态不是BLOCKED,通常是WAITING或TIMED_WAITING。 |
| RUNNABLE → WAITING | object.wait() |
调用wait()会释放当前持有的锁,使线程进入无限期等待,需要其他线程通过notify()/notifyAll()唤醒。 |
thread.join() |
当前线程等待目标线程执行完毕。底层是通过wait()实现。 |
|
LockSupport.park() |
挂起当前线程,等待unpark()或中断。不会释放已经持有的锁。 |
|
| RUNNABLE → TIMED_WAITING | Thread.sleep(long millis) |
使线程休眠指定时间。重要:sleep期间不会释放任何锁。 |
object.wait(long timeout) |
带超时的等待。若超时前未被唤醒,线程将自动恢复。会释放锁。 | |
thread.join(long millis) |
带超时的连接。 | |
LockSupport.parkNanos() / parkUntil() |
带超时的挂起。 | |
| WAITING/TIMED_WAITING → RUNNABLE | object.notify() / notifyAll() |
唤醒在对应对象上等待的单个或所有线程。 |
LockSupport.unpark(thread) |
解除指定线程的挂起状态。 | |
thread.interrupt() |
中断目标线程。如果目标线程正处在WAITING或TIMED_WAITING状态,会抛出InterruptedException并清除中断状态,线程被唤醒。 |
|
| 超时时间到 | 仅适用于TIMED_WAITING状态。 | |
| BLOCKED → RUNNABLE | 持有锁的线程释放了锁。 | 当锁可用时,系统会从所有阻塞的线程中选择一个来获取锁并恢复执行。 |
| RUNNABLE → TERMINATED | run()方法正常执行完毕。 |
线程完成其任务,正常结束。 |
run()方法执行过程中抛出未捕获的异常。 |
线程因异常而意外终止。 |
⚠️ 关键点与最佳实践
- RUNNABLE状态的复合性:Java的RUNNABLE状态涵盖了操作系统的“就绪”和“运行”两种状态。通过
Thread.getState()无法区分线程是在等待CPU时间片还是在执行。 - 谨慎使用
yield():Thread.yield()是给调度器的一个提示,表示当前线程愿意让出CPU。但这是一个不保证有效的建议,调度器可以完全忽略它。不要用它来控制执行顺序。 - 正确处理中断:
interrupt()机制是Java提供的协作式线程取消机制。在编写可能阻塞的代码时,要合理响应中断(捕获InterruptedException并执行清理操作或恢复中断状态),使线程能够被优雅地停止。 - 避免废弃方法:
stop(),suspend(),resume()等方法因其固有的不安全性(容易导致死锁和数据不一致)而被废弃,绝对不要使用。
🔧 如何监控线程状态
- 编程方式:使用
Thread.getState()获取特定线程的状态,或通过ThreadMXBean获取JVM内所有线程的详细信息。 - 可视化工具:利用JConsole、VisualVM等JDK自带工具,可以实时查看线程状态堆栈,非常适合用于调试和性能分析。
public class WaitNotifyDemo {
// 用于同步的共享对象
private final Object lock = new Object();
// 共享消息
private String message = null;
// 生产者线程
class Producer extends Thread {
@Override
public void run() {
synchronized (lock) {
try {
System.out.println("生产者: 正在准备消息...");
Thread.sleep(2000); // 模拟准备消息的耗时
message = "你好,这是给你的消息!"; // 设置消息
System.out.println("生产者: 消息已准备好,通知消费者。");
lock.notify(); // 通知等待的消费者线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 消费者线程
class Consumer extends Thread {
@Override
public void run() {
synchronized (lock) {
try {
System.out.println("消费者: 等待生产者提供消息...");
while (message == null) { // 使用循环防止“虚假唤醒”
lock.wait(); // 释放锁并进入等待
}
System.out.println("消费者: 收到消息 -> " + message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
WaitNotifyDemo demo = new WaitNotifyDemo();
Consumer consumer = demo.new Consumer();
Producer producer = demo.new Producer();
consumer.start(); // 先启动消费者,使其进入等待
Thread.sleep(500); // 稍作延迟,确保消费者先开始等待
producer.start(); // 再启动生产者
// 等待两个线程执行完毕
consumer.join();
producer.join();
System.out.println("主程序: 演示结束。");
}
}
同步与锁机制
多线程环境下,线程安全是至关重要的考虑因素。
synchronized关键字:
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
}
Lock接口(更灵活的选择):
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Lock;
public class LockExample {
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
读写锁(读多写少场景):
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private int value;
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
public void write(int newValue) {
rwLock.writeLock().lock();
try {
value = newValue;
} finally {
rwLock.writeLock().unlock();
}
}
public int read() {
rwLock.readLock().lock();
try {
return value;
} finally {
rwLock.readLock().unlock();
}
}
}
更多的锁相关内容查看另一篇博客:https://blog.csdn.net/weixin_44044929/article/details/156861709
线程间通信
线程间通信确实很重要,它让多个线程能够协同工作、安全地传递数据。下面这个表格汇总了主要的通信方式。
| 通信机制 | 核心原理 | 典型应用场景 | 关键特点 |
|---|---|---|---|
| 共享变量 | 多个线程直接读写同一内存区域 | 简单的状态标志、计数器 | 需配合同步机制(如synchronized或volatile)保证安全 |
| 等待/通知 (Wait/Notify) | 线程在共享对象上等待(wait)或发出通知(notify/notifyAll) |
生产者-消费者模式、线程间条件协调 | 必须获得对象锁(在synchronized块内)使用 |
| 线程同步工具 | 基于消息传递,线程通过线程安全的数据结构(如阻塞队列)交换数据 | 生产者-消费者模式、任务调度 | 解耦生产与消费,内置线程安全与协调机制 |
| Lock与Condition | 提供比synchronized更灵活的锁操作,Condition可实现精确的等待/通知 |
复杂的多条件同步场景(如读写锁) | 可创建多个Condition,实现精准唤醒 |
| 高级同步工具 | 使用CountDownLatch、CyclicBarrier、Semaphore等 |
控制并发线程数(Semaphore),同步多个线程的阶段(CyclicBarrier),等待多个任务完成(CountDownLatch) |
功能专一,简化特定同步逻辑的实现 |
⚙️ 关键细节与最佳实践
了解基本方式后,一些关键细节和最佳实践能帮助你避免常见的“坑”。
-
正确使用Wait-Nofity:
wait()方法会释放当前持有的锁。调用notify()或notifyAll()后,被唤醒的线程并不会立即执行,它们需要重新获取锁。因此,条件检查应放在while循环中,而不是if语句中,这是为了防止虚假唤醒(即线程在没有收到明确通知的情况下被唤醒)。同时,volatile关键字能保证变量的可见性,但不保证复合操作(如count++)的原子性。对于计数等场景,AtomicInteger等原子类通常是更好的选择。 -
优先使用并发工具:在多数情况下,
java.util.concurrent包提供的工具(如BlockingQueue,CountDownLatch等)优于手动使用wait()/notify(),因为它们更成熟,能减少出错。
💎 如何选择通信方式
选择哪种方式取决于具体需求:
- 简单的状态标志或原子更新:考虑
volatile或原子类(如AtomicInteger)。 - 典型的生成者-消费者模式:
BlockingQueue是最直接和推荐的选择。 - 需要在线程间协调复杂的条件(例如“多条件等待”):
Lock配合Condition提供了更精细的控制。 - 需要让多个线程在某个点同步,或控制同时访问特定资源的线程数量:
CountDownLatch、CyclicBarrier、Semaphore等高级同步工具非常合适。 - 需要协调线程执行顺序(如让一个线程等待另一个线程完成):
Thread.join()方法简单有效。
⚠️ 务必避开这些坑
- 死锁:当两个或多个线程互相等待对方释放锁时发生。解决方法是固定锁的获取顺序,或者使用支持超时尝试获取锁的机制。
- 性能损耗:不必要或过粗粒度的同步(如在非常大的代码块上使用
synchronized)会限制并发性能。尽量减小同步代码块的范围。 - 线程活性故障:除了死锁,还要注意活锁(线程不断重试但始终无法进展)和饥饿(某些线程始终得不到执行机会)。
安全的停止线程
在Java中,安全地停止线程是一项重要的编程技能,核心在于采用协作式机制,让线程有机会清理资源并完成必要操作后自行结束,而不是被强制终止。
下面的表格汇总了主要的方法和最佳实践。
| 方法 | 核心机制 | 适用场景 | 关键要点 |
|---|---|---|---|
| 协作式中断标志 | 通过volatile boolean变量作为信号。 |
简单循环任务,需要自定义停止逻辑。 | 实现简单;注意volatile保证可见性。 |
interrupt() 方法 |
调用线程的interrupt()方法设置中断状态,线程通过isInterrupted()检查或通过InterruptedException感知。 |
需要利用Java内置中断机制的场景,尤其是可能阻塞的操作(如sleep(), wait())。 |
JVM内置支持;可中断阻塞操作;需正确恢复中断状态。 |
| 线程池管理 | 通过ExecutorService的shutdown()或shutdownNow()方法停止线程池中所有线程。 |
使用线程池执行任务时。 | 管理多个线程;shutdown()优雅关闭,shutdownNow()尝试立即中断。 |
| Future 的 cancel() | 通过Future对象的cancel(true)方法尝试取消任务的执行。 |
需要取消由线程池执行的单个特定任务。 | 可中断特定任务。 |
| 守护线程 (Daemon) | 将线程设置为守护线程(setDaemon(true)),当JVM中所有非守护线程结束时,守护线程会自动结束。 |
执行辅助或后台任务,这些任务可以随主程序结束而终止,无需单独管理其生命周期。 | 生命周期随JVM结束而终止,不保证finally块执行或资源清理。 |
⚙️ 详解核心方法
1. 协作式中断标志
设置一个volatile布尔变量作为线程执行的信号。
public class CustomFlagThread extends Thread {
private volatile boolean running = true; // volatile确保可见性
public void run() {
while (running) {
// ... 执行任务
}
// ... 清理工作
}
public void stopGracefully() {
running = false;
}
}
优点:简单直观,完全控制中断逻辑。
注意点:volatile关键字确保一个线程对running的修改对其他线程立即可见。
2. 内置中断机制 (interrupt())
这是Java官方推荐的协作式中断方式。
-
检查中断状态:适用于未阻塞的线程。
public class InterruptCheckThread extends Thread { public void run() { // 检查中断状态前可以执行一些工作 while (!Thread.currentThread().isInterrupted()) { // ... 执行任务 } System.out.println("线程已收到中断信号,优雅退出。"); // ... 清理资源 } } // 外部调用 thread.interrupt() 来请求中断 -
处理InterruptedException:当线程在可中断的阻塞方法(如
Thread.sleep(),Object.wait(),Thread.join())中时,调用interrupt()会触发InterruptedException。public class InterruptSleepThread extends Thread { public void run() { try { while (!Thread.currentThread().isInterrupted()) { // ... 执行部分任务 Thread.sleep(1000); // 休眠时可能被中断 } } catch (InterruptedException e) { // 捕获到中断异常,恢复中断状态是良好实践 Thread.currentThread().interrupt(); System.out.println("线程在休眠中被中断。"); } // ... 清理资源 } }关键:在捕获
InterruptedException后,通常需要再次调用Thread.currentThread().interrupt()来重新设置中断状态,因为异常会清除中断状态。
3. 线程池的停止
使用ExecutorService时,停止线程应通过它提供的方法。
shutdown():平缓关闭,不再接受新任务,已提交任务执行完毕。shutdownNow():尝试立即停止所有正在执行的任务(通过调用Thread.interrupt()),并返回等待执行的任务列表。ExecutorService executor = Executors.newFixedThreadPool(2); executor.submit(() -> { while (!Thread.currentThread().isInterrupted()) { // ... 执行任务 } }); // ... List<Runnable> notExecutedTasks = executor.shutdownNow(); // 尝试中断所有任务
4. 使用Future取消任务
提交任务到线程池会返回Future对象,可用其取消特定任务。
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<?> future = executor.submit(() -> {
// ... 长时间运行的任务
});
// ...
boolean mayInterruptIfRunning = true;
future.cancel(mayInterruptIfRunning); // 尝试取消任务执行
⚠️ 强烈避免的方法
绝对不要使用Thread.stop()方法。该方法已被废弃,因为它会立即强制停止线程,可能导致:
- 数据不一致:线程可能正在更新共享数据,强制停止会使数据处于损坏状态。
- 资源泄漏:线程可能持有锁或其他资源,未来得及释放,进而引发死锁或资源耗尽。
💡 最佳实践与要点
- 优先使用
interrupt()机制:这是Java语言设计的标准方式,能与很多库函数良好协作。 - 明智处理中断:不要忽略
InterruptedException。根据任务性质选择:传递异常(声明抛出)、恢复中断状态(再次设置中断标志)或优雅退出。 - 确保资源清理:无论以何种方式结束线程,一定要在
finally块中释放资源(如关闭文件、数据库连接)。 - 平衡响应性与性能:中断检查频率需权衡。过于频繁可能影响性能,过于稀疏可能导致中断响应延迟。
📊 如何选择停止方法?
- 大多数需要协作停止的场景:首选
interrupt()机制。 - 需要高度自定义停止逻辑或简单循环任务:可使用协作式标志位。
- 使用线程池管理多线程:务必使用**
ExecutorService的关闭方法和Future.cancel()**。 - 后台辅助任务,允许随主线程结束:可考虑设为守护线程。
🛡️ 多线程开发中的注意事项
1. 线程安全
多个线程可能会同时访问共享资源,从而导致数据不一致的问题。可以使用同步机制,如synchronized关键字、ReentrantLock类等来解决。
2. 死锁预防
死锁是指两个或多个线程相互等待对方释放资源,从而导致线程永远无法继续执行的现象。为了避免死锁,需要注意资源的获取顺序,并尽量减少锁的使用。
3. 性能优化
- 使用线程池减少线程创建和销毁的开销
- 根据任务类型(CPU密集型/IO密集型)合理配置线程数
- 使用并发集合(如ConcurrentHashMap、CopyOnWriteArrayList)避免手动同步
4. 资源管理
- 确保在使用多线程时合理管理资源
- 在使用Executor框架时,记得调用shutdown()方法关闭线程池
💻 实际应用场景
多线程编程在现实场景中有广泛的应用:
- 网络服务器:通过多线程处理多个客户端请求,提高服务器的并发处理能力
- 图像处理:将图像拆分成多个部分并行处理,加速处理过程
- 爬虫程序:使用多线程并发抓取多个网页,提高爬取效率
- 游戏开发:多线程可以用于处理游戏中的不同操作,如渲染、输入等
更多推荐


所有评论(0)