Java 线程基本概念、使用方法最全详解
作者:猩火燎猿
一、什么是线程
线程是程序执行的最小单位,是进程中的一个执行流。一个进程可以包含多个线程,这些线程共享进程的资源,但有各自的执行路径。
二、Java线程的基本使用
1. 创建线程的方式
(1)继承 Thread 类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程运行:" + Thread.currentThread().getName());
}
}
MyThread t1 = new MyThread();
t1.start(); // 启动线程(2)实现 Runnable 接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程运行:" + Thread.currentThread().getName());
}
}
Thread t2 = new Thread(new MyRunnable());
t2.start();(3)实现 Callable 接口(带返回值)
import java.util.concurrent.*;
class MyCallable implements Callable<Integer> {
@Override
public Integer call() {
System.out.println("线程运行:" + Thread.currentThread().getName());
return 123;
}
}
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(new MyCallable());
Integer result = future.get(); // 阻塞获取返回值
executor.shutdown();2. 线程的生命周期状态
- NEW:新建
- RUNNABLE:可运行(正在运行或等待CPU)
- BLOCKED:阻塞,等待锁
- WAITING:等待(无超时,等待其他线程唤醒)
- TIMED_WAITING:超时等待(如 sleep、wait(long))
- TERMINATED:终止
3. 线程常用方法
start():启动线程run():线程执行体(不要直接调用)sleep(ms):让当前线程休眠join():等待指定线程执行完毕interrupt():中断线程isAlive():判断线程是否还在运行yield():让出CPU执行权
三、线程的同步和安全
由于多个线程共享内存,容易出现线程安全问题,常用的同步方式有:
synchronized关键字(方法或代码块)Lock接口(如ReentrantLock)- 原子类(如
AtomicInteger) - 并发集合(如
ConcurrentHashMap)
四、线程间通信
常用方式:
wait()、notify()、notifyAll()(Object类方法,配合synchronized使用)Condition(配合Lock使用)
五、线程状态
Java线程的六种状态(Thread.State 枚举)
- NEW(新建)
- 线程对象刚被创建,还没有调用
start()方法。
- 线程对象刚被创建,还没有调用
- RUNNABLE(可运行)
- 调用
start()方法后,线程处于可运行状态,可能正在运行,也可能正在等待操作系统分配CPU资源。
- 调用
- BLOCKED(阻塞)
- 线程在等待获取一个监视器锁(即 synchronized),无法继续执行。
- WAITING(等待)
- 线程在等待其他线程执行特定动作(如
Object.wait()、Thread.join()、LockSupport.park()),不会自动唤醒,需其他线程唤醒。
- 线程在等待其他线程执行特定动作(如
- TIMED_WAITING(计时等待)
- 线程在指定时间内等待(如
Thread.sleep(long)、Object.wait(long)、Thread.join(long)、LockSupport.parkNanos()),时间到后自动唤醒。
- 线程在指定时间内等待(如
- TERMINATED(终止)
- 线程执行完毕或出现异常,生命周期结束。
状态转换示意图
NEW ↓ RUNNABLE ←→ BLOCKED ↓ ↕ WAITING ←→ TIMED_WAITING ↓ TERMINATED
示例代码
Thread t = new Thread(() -> {
System.out.println("线程运行中");
});
System.out.println(t.getState()); // NEW
t.start();
System.out.println(t.getState()); // RUNNABLE线程状态查询
通过 Thread.getState() 方法可以获得当前线程的状态,返回值是上面这些枚举类型。
使用 jstack 工具
jstack 是 JDK 自带的线程堆栈分析工具。它可以输出所有线程的堆栈信息,包括线程状态和锁的详细信息。
步骤:
找到你的 Java 进程 PID(比如用 jps 命令)。
执行:
jstack <PID>
在输出中查找 BLOCKED 状态的线程,会显示类似:
"Thread-1" #12 prio=5 os_prio=0 tid=0x000000001a2b3800 nid=0x1c34 BLOCKED on object monitor owned by "Thread-2" tid=0x000000001a2b4000 at com.example.MyClass.myMethod(MyClass.java:23) - blocked on <0x000000076b1c1b20> (a java.lang.Object) - locked <0x000000076b1c1b20> (a java.lang.Object)
blocked on <0x000000076b1c1b20> 表示线程正在等待这个对象的锁。
owned by "Thread-2" 表示这个锁当前被哪个线程持有。
2. 使用可视化工具
- VisualVM、JConsole 等工具可以图形化地查看线程状态和锁的持有情况。
- 在“线程”面板里,可以看到线程状态,点击具体线程可以查看堆栈和锁情况。
3. 代码层面辅助排查
虽然不能直接在 Java 代码中获取“当前 BLOCKED 等待哪个锁”,但可以通过合理设计日志、监控代码,辅助定位。例如:
- 在 synchronized 代码块前后打印日志,标记线程进入和退出锁的时间。
- 利用
ThreadMXBean获取线程堆栈信息(但锁对象地址需结合堆栈分析)。
4. 结合堆栈分析
通过堆栈可以看到线程卡在哪一行代码,通常是 synchronized 相关的代码块或方法。
六、什么是线程池
线程池(Thread Pool)是一种线程管理方式。它预先创建一批线程,任务来了就复用这些线程,而不是每次都新建/销毁线程,从而提高性能,减少系统开销。
七、线程池的核心优势
- 降低资源消耗:避免频繁创建/销毁线程。
- 提高响应速度:任务到来时可直接复用现有线程。
- 便于管理:可统一分配、调度线程,提高系统稳定性。
八、Java中的线程池实现
Java 提供了线程池相关的 API,主要在 java.util.concurrent 包下。
1. 主要类和接口
- Executor:线程池的顶层接口,定义了任务提交的方法。
- ExecutorService:继承 Executor,增加了生命周期管理等功能。
- ThreadPoolExecutor:线程池的核心实现类,可高度定制。
- Executors:工具类,提供常用线程池的工厂方法。
2. 常用线程池创建方式
ExecutorService pool = Executors.newFixedThreadPool(5); // 固定大小线程池 ExecutorService pool = Executors.newCachedThreadPool(); // 可变大小线程池 ExecutorService pool = Executors.newSingleThreadExecutor(); // 单线程池 ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(2); // 定时任务线程池
建议: 实际项目中推荐直接使用
ThreadPoolExecutor,而不是Executors工厂方法,因为可以更细致地配置参数,避免资源耗尽等风险。
九、ThreadPoolExecutor核心参数
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize, // 核心线程数
maximumPoolSize, // 最大线程数
keepAliveTime, // 非核心线程存活时间
unit, // 时间单位
workQueue, // 任务队列
threadFactory, // 线程工厂
handler // 拒绝策略
);- corePoolSize:常驻线程数。
- maximumPoolSize:最大线程数。
- keepAliveTime:非核心线程闲置多久后回收。
- workQueue:任务队列,如
LinkedBlockingQueue。 - threadFactory:自定义线程创建方式。
- handler:拒绝策略(任务太多无法处理时的应对措施)。
十、线程池工作流程图
提交任务 ↓ 线程池检查是否有空闲线程 ↓ 有 → 直接执行 无 → 进入任务队列 ↓ 队列满 → 创建新线程(不超过最大线程数) ↓ 线程池满/队列满 → 拒绝策略
十一、线程池常用方法
- execute(Runnable command):提交任务,无返回值。
- submit(Callable task):提交任务,有返回值(Future)。
- shutdown():平滑关闭线程池,等待任务执行完毕。
- shutdownNow():立即关闭,尝试打断正在执行的任务。
十二、简单示例
ExecutorService pool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
pool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 执行任务");
});
}
pool.shutdown();十二、线程池使用注意事项
- 合理配置参数:结合实际业务和机器资源设置线程池参数。
- 避免任务堆积:队列过长可能导致内存溢出。
- 自定义拒绝策略:根据业务需求处理无法执行的任务。
- 线程安全:任务本身要保证线程安全。
十三、总结
- 线程池是高效管理线程的关键工具。
- 推荐使用
ThreadPoolExecutor并合理配置参数。 - 线程池的合理使用能显著提升系统性能和稳定性。
十四、什么是拒绝策略?
当线程池中的任务数量超过了线程池的最大承载能力(即:线程池最大线程数和任务队列都满了),此时再提交新任务,线程池就会触发拒绝策略,决定如何处理这些无法立即执行的任务。
十五、拒绝策略的触发条件
线程池执行任务的流程大致如下:
- 线程数 < corePoolSize:创建新线程执行任务。
- 线程数 ≥ corePoolSize,队列未满:任务入队列等待执行。
- 队列满,线程数 < maximumPoolSize:创建新线程执行任务。
- 线程数 = maximumPoolSize,队列也满:触发拒绝策略。
十六、Java内置的4种拒绝策略
Java通过java.util.concurrent.ThreadPoolExecutor提供了4种常用的拒绝策略,分别实现了RejectedExecutionHandler接口:
1. AbortPolicy(默认策略)
直接抛出异常(
RejectedExecutionException),阻止系统正常运行。
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}适用场景:希望程序员能及时发现问题,适合对任务丢失不能容忍的场合。
2. CallerRunsPolicy
由调用线程(提交任务的线程)自己执行该任务,不会抛异常。
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}适用场景:希望不丢任务,但会降低提交任务线程的速度,间接缓解线程池压力。
3. DiscardPolicy
直接丢弃任务,不做任何处理,也不抛异常。
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
// 什么都不做,任务被丢弃
}
}适用场景:对丢失部分任务可以容忍,不希望抛异常影响主流程。
4. DiscardOldestPolicy
丢弃队列中最早的(最旧的)一个任务,然后尝试再次提交当前任务。
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll(); // 移除队列头部的一个任务
e.execute(r); // 再尝试提交当前任务
}
}
}适用场景:优先保证新任务执行,适合对旧任务时效性要求不高的场合。
十七、自定义拒绝策略
你可以实现 RejectedExecutionHandler 接口,自定义处理逻辑,例如:
- 记录日志报警
- 任务入库持久化
- 发送告警邮件
- 其他业务补偿措施
示例代码:
public class MyRejectHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("任务被拒绝:" + r.toString());
// 这里可以做日志、报警、持久化等操作
}
}使用方法:
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
new MyRejectHandler()
);十八、实际应用建议
- 高并发场景建议合理设置线程池参数,避免频繁触发拒绝策略。
- 对于关键任务,可用 CallerRunsPolicy 或自定义策略,避免任务丢失。
- 对于非关键任务,可用 DiscardPolicy,保证主流程不被影响。
- 监控与报警:拒绝策略中建议加日志和报警,便于及时发现问题。
到此这篇关于Java 线程最全详解的文章就介绍到这了,更多相关Java 线程内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
