线程是Java并发编程的核心概念,是程序执行的最小单元。Java从最初版本就提供了线程支持,并随着发展不断增强了线程相关API。
一、线程基本概念
1. 线程与进程的区别
- 进程:操作系统资源分配的基本单位,有独立的内存空间
- 线程:CPU调度的基本单位,共享进程的内存空间
2. Java线程的特点
- 每个Java程序至少有一个主线程(main线程)
- 线程共享堆内存,但有独立的栈空间
- Java线程调度由JVM和操作系统共同管理
二、线程创建方式
1. 继承Thread类
优点:
- 简单直接:只需重写run()方法即可
- 访问简单:可直接使用this获取当前线程对象
class MyThread extends Thread {
public void run() {
System.out.println("Thread running");
}
}
// 使用
new MyThread().start();
缺点:
- 单继承限制:Java不支持多继承,影响扩展性
- 职责不单一:将任务与线程控制耦合在一起
- 资源浪费:每次新建任务需创建新线程对象
2. 实现Runnable接口
优点:
- 接口灵活:可实现多个接口,扩展性好
- 职责分离:任务逻辑与线程控制解耦
- 资源共享:适合多线程处理同一任务
class MyRunnable implements Runnable {
public void run() {
System.out.println("Task running");
}
}
// 使用
new Thread(new MyRunnable()).start();
缺点:
- 无返回值:run()方法返回void
- 功能有限:缺少线程控制方法(如暂停、停止)
- 仍需创建Thread:本质上还是依赖Thread类
3. 实现Callable接口
优点:
- 支持返回值:通过Future获取计算结果
- 异常处理:可抛出checked exception
- 配合线程池:与ExecutorService完美集成
class MyCallable implements Callable<String> {
public String call() throws Exception {
return "Result";
}
}
// 使用
FutureTask<String> future = new FutureTask<>(new MyCallable());
new Thread(future).start();
String result = future.get();
缺点:
- 使用复杂:需要配合Future/FutureTask使用
- 阻塞获取:future.get()会阻塞当前线程
- 仍需包装:单独使用时仍需转为Runnable
4. 使用线程池(ExecutorService)
优点:
- 资源复用:避免频繁创建销毁线程
- 管理方便:提供线程生命周期管理
- 功能丰富:支持定时/延时任务
- 负载控制:可限制最大并发数
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(() -> {
System.out.println("Pool task");
});
executor.shutdown();
缺点:
- 配置复杂:需要合理设置参数(核心/最大线程数等)
- 资源泄漏风险:忘记关闭会导致线程泄漏
- 调试困难:线程池内部问题较难排查
综合对比表
创建方式 | 返回值支持 | 异常处理 | 资源消耗 | 扩展性 | 适用场景 |
---|---|---|---|---|---|
继承Thread | ❌ | ❌ | 高 | 差 | 简单演示/测试 |
实现Runnable | ❌ | ❌ | 中 | 好 | 大多数无返回值的线程任务 |
实现Callable | ✔️ | ✔️ | 中 | 好 | 需要返回结果或异常处理的场景 |
使用线程池 | ✔️ | ✔️ | 低 | 最好 | 生产环境高并发场景 |
三、线程生命周期
Java线程有以下6种状态(Thread.State枚举):
状态 | 枚举值 | 特征描述 | 进入条件 | 退出条件 | 常用方法 |
---|---|---|---|---|---|
NEW | Thread.State.NEW | 线程对象已创建但未启动 | 通过new Thread() 创建线程对象 | 调用start() 方法 | new Thread() |
RUNNABLE | Thread.State.RUNNABLE | 线程可能在运行或等待CPU时间片 | 调用start() 后自动进入从阻塞/等待状态恢复 | 被系统调度器选中执行 主动让出CPU( yield() )进入同步阻塞/等待状态 | start() yield() |
BLOCKED) | Thread.State.BLOCKED | 等待获取监视器锁(同步锁) | 尝试进入synchronized 代码块/方法但锁被占用 | 成功获取对象锁 | synchronized ReentrantLock.lock() |
WAITING | Thread.State.WAITING | 无限期等待其他线程唤醒 | 调用无超时参数的wait() join() 或LockSupport.park() | 被其他线程notify()/notifyAll() 目标线程终止(针对 join() )被中断 | Object.wait() Thread.join() LockSupport.park() |
TIMED_WAITING | Thread.State.TIMED_WAITING | 有限时间等待 | 调用带超时参数的sleep() wait(timeout) join(timeout) 等 | 超时时间到 被提前唤醒/中断 | Thread.sleep() Object.wait(timeout) Thread.join(timeout) |
TERMINATED) | Thread.State.TERMINATED | 线程执行完毕或异常终止 | run() 方法正常结束未捕获异常导致线程终止 | 无(最终状态) | - |
四、线程池
Java线程池详解
线程池是Java并发编程中一个非常重要的概念,它提供了一种高效管理线程的机制,可以避免频繁创建和销毁线程带来的性能开销。
4.1.线程池基本概念
线程池(Thread Pool)是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。
为什么需要线程池
- 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁的开销
- 提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行
- 提高线程的可管理性:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性
4.2.Java线程池核心类
Java通过java.util.concurrent.Executors
工厂类提供线程池的创建方法,底层都是通过ThreadPoolExecutor
类实现的。
1. ThreadPoolExecutor构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数说明
-
corePoolSize
- 核心线程数:线程池中保持存活的最小线程数,即使这些线程处于空闲状态。
- 当新任务提交时,如果当前线程数 <
corePoolSize
,线程池会创建新线程处理任务(即使有空闲线程)。 - 默认情况下,核心线程会一直存活,除非设置
allowCoreThreadTimeOut(true)
。
-
maximumPoolSize
- 最大线程数:线程池允许创建的最大线程数量。
- 当任务队列已满且当前线程数 <
maximumPoolSize
,线程池会创建新线程处理任务。 - 如果队列无界(如
LinkedBlockingQueue
),此参数无效。
-
keepAliveTime
+unit
- 空闲线程存活时间:当线程数 >
corePoolSize
时,空闲线程在终止前等待新任务的最长时间。 - 时间单位由
unit
指定(如TimeUnit.SECONDS
)。 - 若设置
allowCoreThreadTimeOut(true)
,核心线程也会受此限制。
- 空闲线程存活时间:当线程数 >
-
workQueue
- 任务队列:用于保存等待执行的任务的阻塞队列。常用实现:
ArrayBlockingQueue
:有界队列,需指定容量。LinkedBlockingQueue
:无界队列(默认Integer.MAX_VALUE
),可能导致 OOM。SynchronousQueue
:不存储任务,直接提交给线程(需配合合理的maximumPoolSize
)。PriorityBlockingQueue
:带优先级的无界队列。
- 任务队列:用于保存等待执行的任务的阻塞队列。常用实现:
-
threadFactory
- 线程工厂:用于创建新线程,可自定义线程名称、优先级、是否为守护线程等。
- 默认使用
Executors.defaultThreadFactory()
,创建的线程名类似pool-N-thread-M
。
-
handler
- 拒绝策略:当线程池和队列已满时的处理策略。常用实现:
AbortPolicy
(默认):抛出RejectedExecutionException
。CallerRunsPolicy
:由提交任务的线程直接执行任务。DiscardPolicy
:静默丢弃任务。DiscardOldestPolicy
:丢弃队列中最旧的任务,然后重试提交。- 自定义策略:实现
RejectedExecutionHandler
接口。
- 拒绝策略:当线程池和队列已满时的处理策略。常用实现:
2. Executors提供的常用线程池
-
FixedThreadPool:固定大小的线程池
Executors.newFixedThreadPool(int nThreads)
-
CachedThreadPool:可缓存的线程池
Executors.newCachedThreadPool()
-
SingleThreadExecutor:单线程的线程池
Executors.newSingleThreadExecutor()
-
ScheduledThreadPool:定时任务的线程池
Executors.newScheduledThreadPool(int corePoolSize)
4.3.线程池工作原理
-
当提交一个新任务时:
- 如果当前运行的线程数 < corePoolSize,则创建新线程执行任务
- 如果运行的线程数 >= corePoolSize,则将任务放入workQueue
- 如果workQueue已满且运行的线程数 < maximumPoolSize,则创建新线程执行任务
- 如果workQueue已满且运行的线程数 >= maximumPoolSize,则执行拒绝策略
-
当线程空闲时间超过keepAliveTime时:
- 如果当前线程数 > corePoolSize,则销毁该线程
- 核心线程默认不会销毁,除非设置allowCoreThreadTimeOut为true
4.4.线程池任务队列
Java线程池常用的阻塞队列:
- ArrayBlockingQueue:基于数组的有界阻塞队列
- LinkedBlockingQueue:基于链表的阻塞队列(默认无界)
- SynchronousQueue:不存储元素的阻塞队列
- PriorityBlockingQueue:具有优先级的无界阻塞队列
4.5.拒绝策略
当线程池无法处理新任务时(队列满且线程数达到最大值),会执行拒绝策略:
- AbortPolicy(默认):直接抛出RejectedExecutionException异常
- CallerRunsPolicy:由调用线程处理该任务
- DiscardPolicy:直接丢弃任务
- DiscardOldestPolicy:丢弃队列中最老的任务,然后尝试重新提交当前任务
4.6.线程池使用示例
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // 核心线程数
4, // 最大线程数
60, // 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(10), // 任务队列
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
// 提交任务
for (int i = 0; i < 15; i++) {
final int taskNum = i;
executor.execute(() -> {
System.out.println("执行任务 " + taskNum + ",线程:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executor.shutdown();
}
}
4.7.线程池最佳实践
- 根据任务类型选择合适的线程池
- 合理设置线程池参数(核心线程数、最大线程数、队列容量)
- 为线程池设置有意义的名称(通过自定义ThreadFactory)
- 监控线程池运行状态(如任务队列大小、活跃线程数等)
- 手动 new ThreadPoolExecutor 来创建线程池,防止资源耗尽导致 OOM 问题
4.8.线程池状态
线程池有5种状态:
- RUNNING:接受新任务并处理队列中的任务
- SHUTDOWN:不接受新任务,但处理队列中的任务
- STOP:不接受新任务,不处理队列中的任务,中断正在执行的任务
- TIDYING:所有任务已终止,workerCount为0,将执行terminated()方法
- TERMINATED:terminated()方法执行完成