2051 字
10 分钟
Java---Thread
Thread
定义
Thread
是Java对线程的直接抽象,每个线程代表独立的执行流程。JVM启动时默认会创建多个系统线程(如main线程
、GC线程
等)
创建方式
- 继承
Thread
类:重写run()方法,直接调用start()启动线程(适用于简单场景,但受限于单继承) - 实现
Runnable
接口:将任务与线程分离,通过Thread构造器传递Runnable实例,支持资源共享(推荐,灵活且避免继承限制) Callable
与FutureTask
:支持返回结果和异常处理,通过FutureTask包装Callable任务,再传递给Thread执行。
实现Runnable vs 继承Thread
设计架构与灵活性
- 继承Thread:由于Java不支持多继承,若子类继承了
Thread
,则无法再继承其他类 - 实现Runnable接口:接口允许多实现,任务类可以继承其他父类,代码扩展性更强
任务与线程的分离
Runnable
仅定义任务逻辑(通过run()
方法),与线程管理解耦,便于复用任务Thread
将任务与线程生命周期绑定,导致代码耦合度高
资源共享能力
- Runnable:多个线程可共享同一
Runnable
实例,天然支持资源共享(如售票系统共用票数变量) - Thread:每个线程需创建独立的
Thread
实例,共享资源需额外同步机制(如静态变量),易引发竞态条件
并发工具兼容性
- Runnable:与Java并发工具(如
Executor
框架、Future
)无缝集成,支持异步任务调度 - Thread:需手动管理线程生命周期,难以适配高级并发模型
代码复用性
- Runnable:任务逻辑独立于线程,同一任务可被不同线程或线程池执行,提升代码复用
- Thread:任务与线程绑定,复用性差
调试与维护
- Runnable:任务逻辑集中,便于单元测试和维护
- Thread:线程管理与业务逻辑混杂,调试复杂度高
样例
实现Runnable接口
// 实现 Runnable 接口,通过构造函数传参
public class MyRunnable implements Runnable {
private String param1; // 线程参数1
private int param2; // 线程参数2
// 构造函数接收参数
public MyRunnable(String param1, int param2) {
this.param1 = param1;
this.param2 = param2;
}
@Override
public void run() {
System.out.println("Runnable线程执行,参数1=" + param1 + ",参数2=" + param2);
}
public static void main(String[] args) {
// 创建 Runnable 实例并传递参数
MyRunnable task = new MyRunnable("Hello", 100);
// 通过 Thread 启动线程
Thread thread = new Thread(task);
thread.start();
}
}
说明
参数传递:通过构造函数将参数注入 MyRunnable 类的成员变量,线程启动后 run() 方法直接使用这些参数
优势:支持多参数传递,且线程逻辑与参数完全解耦,可复用性强
Lambda 简化版(适用于简单场景):
// 直接通过 Lambda 传递参数 new Thread(() -> System.out.println("参数=" + 42)).start();
继承Thread类
// 继承 Thread 类,通过构造函数传参
public class MyThread extends Thread {
private String param1;
private int param2;
// 构造函数接收参数
public MyThread(String param1, int param2) {
this.param1 = param1;
this.param2 = param2;
}
@Override
public void run() {
System.out.println("Thread线程执行,参数1=" + param1 + ",参数2=" + param2);
}
public static void main(String[] args) {
// 直接创建子类实例并启动线程
MyThread thread = new MyThread("World", 200);
thread.start();
}
}
说明
- 参数传递:通过子类构造函数直接初始化参数,run() 方法调用时直接使用
- 限制:每个线程必须单独创建 Thread 子类实例,无法共享任务对象,导致资源利用率较低
- 注意点:避免直接调用 run() 方法(会导致同步执行而非启动新线程)
核心属性和方法
属性
- ID:唯一标识符 (
getId()
) - 名称:调试用(
getName()
) - 状态:线程生命周期状态(如
NEW
、RUNNABLE
等) - 优先级:1-10级(默认5,对调度影响有限)
- 前台/后台线程:
- 前台线程阻止JVM退出(默认),后台线程(守护线程)不会(
setDaemon(true)
)。
- 前台线程阻止JVM退出(默认),后台线程(守护线程)不会(
关键方法
start()
:启动线程,触发run()
执行sleep()
:阻塞当前线程,不释放锁join()
:等待线程终止interrupt()
:中断线程(需配合状态检查)
线程安全与资源管理
- 资源共享:多个线程访问共享变量需同步(如
synchronized
或Lock
) - 使用场景:
- Thread:适合无需共享资源、简单任务的场景
- Runnable:需资源共享、代码复用或避免继承限制时优先选择
ThreadLocal(线程本地变量)
作用
数据隔离:为每个线程提供独立的变量副本,避免多线程竞争(如数据库连接、用户会话等场景)
示例:
private static ThreadLocal<String> threadLocal = new ThreadLocal<>(); threadLocal.set("data"); // 当前线程独有 String data = threadLocal.get();
实现原理(底层原理)
ThreadLocalMap:
- 每个
Thread
内部维护一个ThreadLocalMap
,以ThreadLocal
对象为键(弱引用),存储线程专属的值(强引用) - set/get流程:
set()
:将键值对存入当前线程的ThreadLocalMap
。get()
:从当前线程的Map中查找值,若未初始化则调用initialValue()
内存泄漏风险与解决方案
风险来源:
在线程池中使用
ThreadLocal
会造成内存泄漏,ThreadLocal
对象被回收后,Map中的键(弱引用)变为null
,但值仍被强引用,导致无法回收规避措施:
显式调用
remove()
:使用完ThreadLocal
后手动清理条目配合
try-finally
或AutoCloseable
:确保资源释放(如连接池场景)try { threadLocal.set(value); // 业务逻辑 } finally { threadLocal.remove(); }
应用场景
线程私有资源管理:如数据库连接、用户身份信息
避免参数传递:跨方法调用时隐式传递上下文(如
Spring
的RequestContextHolder
)
Executor
定义
Executors
是Java并发包(java.util.concurrent
)中提供的线程池工厂工具类,用于简化线程池的创建和管理。它通过静态工厂方法生成不同类型的线程池实例(ExecutorService
接口实现类),开发者无需手动配置核心参数即可快速使用线程池。
核心功能
- 创建线程池:提供多种预定义线程池类型(如固定大小、缓存型、单线程池等)
- 任务调度:支持定时/周期性任务(通过
ScheduledExecutorService
实现) - 线程复用:通过线程池复用线程资源,避免频繁创建/销毁线程的开销
常用方法及场景
方法名 | 线程池类型 | 特点 | 适用场景 |
---|---|---|---|
newFixedThreadPool(n) | 固定大小线程池 | 核心线程数=最大线程数,队列无界(LinkedBlockingQueue ) | 负载稳定、任务量可控的场景 |
newCachedThreadPool() | 缓存型线程池 | 线程数无限(60秒闲置回收),任务直接执行无队列 | 短生命周期、高并发短期任务 |
newSingleThreadExecutor() | 单线程池 | 仅1个线程顺序执行任务,队列无界 | 需顺序执行任务的场景 |
newScheduledThreadPool(n) | 定时线程池 | 支持延迟/周期性任务,核心线程数固定 | 定时任务、心跳检测等场景 |
使用步骤(以newFixedThreadPool为例)
// 1. 创建线程池(固定3个线程)
ExecutorService executor = Executors.newFixedThreadPool(3);
// 2. 提交任务(支持Runnable/Callable)
executor.execute(() -> {
System.out.println("Task executed by " + Thread.currentThread().getName());
});
// 3. 关闭线程池(优雅终止)
executor.shutdown();
// 可选:等待所有任务完成(超时60秒)
executor.awaitTermination(60, TimeUnit.SECONDS);
优缺点分析
优点
- 简化开发 无需手动配置核心参数(如核心线程数、队列类型),一行代码即可创建线程池 示例:
Executors.newCachedThreadPool()
自动处理线程回收和任务调度 - 性能优化 线程复用减少资源消耗(如
newFixedThreadPool
复用固定线程) 缓存型线程池(newCachedThreadPool
)动态扩缩容,适合处理突发流量 - 功能丰富 支持延迟任务(
schedule()
)和周期性任务(scheduleAtFixedRate()
)
缺点及风险
- 潜在OOM风险 无界队列(如FixedThreadPool使用LinkedBlockingQueue)可能导致任务堆积,内存溢出 缓存线程池(newCachedThreadPool)无最大线程数限制,可能创建过多线程耗尽资源
- 灵活性不足 无法自定义拒绝策略或队列类型(默认使用AbortPolicy和LinkedBlockingQueue) 例如:无法直接配置有界队列或自定义任务丢弃策略
- 隐藏底层细节 实际参数配置不透明(如newSingleThreadExecutor的队列容量为Integer.MAX_VALUE),调试困难