前言
多线程编程是 Java 开发者必须掌握的核心技能,而了解线程创建的不同方式及其内部机制,是构建高效稳定并发程序的基础。本文将通过实例代码、原理分析和源码解读,全面剖析 Java 中创建线程的四种主要方式,帮助开发者选择最适合自己业务场景的线程创建方法。
一、继承 Thread 类创建线程
1.1 基本原理
Thread 类是 Java 中表示线程的核心类,它实现了 Runnable 接口。通过继承 Thread 类创建线程是最直接的方式,只需要重写 run()方法。
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getName() + "正在执行");
// 线程执行逻辑
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 创建线程对象
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
// 设置线程名称
thread1.setName("Thread-1");
thread2.setName("Thread-2");
// 启动线程
thread1.start();
thread2.start();
}
}
1.2 源码解析
当调用start()
方法时,JVM 会为线程分配资源并调用start0()
本地方法,最终导致run()
方法在新线程中执行:
// Thread类的start()方法简化版源码
public synchronized void start() {
if (threadStatus != 0) throw new IllegalThreadStateException(); // 确保线程未启动
group.add(this); // 添加到线程组
start0(); // 核心:调用本地方法启动新线程
}
// 本地方法,由JVM实现
private native void start0();
1.3 注意事项
- 必须调用
start()
方法而非直接调用run()
方法,后者只会在当前线程中执行,不会创建新线程 - 一个 Thread 实例只能被启动一次,多次调用会抛出 IllegalThreadStateException 异常
- 继承 Thread 类后无法再继承其他类,因为 Java 是单继承的
二、实现 Runnable 接口创建线程
2.1 基本原理
Runnable 接口是 Java 多线程的核心接口,它只包含一个run()
方法。实现该接口的类可以交给 Thread 执行。
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getName() + "正在执行");
// 线程执行逻辑
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 创建Runnable实现类对象
MyRunnable myRunnable = new MyRunnable();
// 创建Thread对象,传入Runnable实例
Thread thread1 = new Thread(myRunnable, "Thread-1");
Thread thread2 = new Thread(myRunnable, "Thread-2");
// 启动线程
thread1.start();
thread2.start();
}
}
2.2 Lambda 表达式简化
Java 8 后可以使用 Lambda 表达式简化 Runnable 实现:
public class RunnableLambdaDemo {
public static void main(String[] args) {
// 使用Lambda表达式创建Runnable
Runnable task = () -> {
String threadName = Thread.currentThread().getName();
System.out.println("线程" + threadName + "开始执行");
for (int i = 0; i < 5; i++) {
System.out.println(threadName + ":" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 创建并启动线程
new Thread(task, "Lambda-Thread-1").start();
new Thread(task, "Lambda-Thread-2").start();
}
}
2.3 Thread 与 Runnable 的关系
Thread 类其实是 Runnable 接口的实现类。当 Thread 接收一个 Runnable 参数时,内部会保存这个 Runnable 引用,并在自己的 run()方法中调用它:
// Thread类的部分源码
public class Thread implements Runnable {
/* Runnable 对象,如果通过构造函数传入 */
private Runnable target;
// 构造函数接收Runnable参数
public Thread(Runnable target) {
this.target = target;
}
// run方法实现
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
三、实现 Callable 接口配合 Future 获取返回值
3.1 基本原理
Runnable 接口无法返回执行结果,也不能抛出受检异常,而 Callable 接口克服了这些限制。它是一个泛型接口,泛型参数表示返回值类型。Future 接口则用于获取异步计算结果。
import java.util.concurrent.*;
public class CallableDemo {
public static void main(String[] args) throws Exception {
// 创建Callable对象
Callable<Integer> task = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("线程" + Thread.currentThread().getName() + "开始计算");
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
Thread.sleep(10); // 模拟耗时操作
}
return sum;
}
};
// 使用Lambda简化
Callable<Integer> lambdaTask = () -> {
System.out.println("Lambda线程" + Thread.currentThread().getName() + "开始计算");
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
Thread.sleep(5); // 模拟耗时操作
}
return sum;
};
// 创建FutureTask
FutureTask<Integer> futureTask1 = new FutureTask<>(task);
FutureTask<Integer> futureTask2 = new FutureTask<>(lambdaTask);
// 启动线程
new Thread(futureTask1, "Future-1").start();
new Thread(futureTask2, "Future-2").start();
// 获取结果(阻塞)
System.out.println("等待计算结果...");
Integer result1 = futureTask1.get(); // 阻塞直到计算完成
System.out.println("Future-1计算结果:" + result1);
// 设置超时时间
try {
Integer result2 = futureTask2.get(2, TimeUnit.SECONDS);
System.out.println("Future-2计算结果:" + result2);
} catch (TimeoutException e) {
System.out.println("计算超时!");
futureTask2.cancel(true); // 尝试取消任务
}
}
}
3.2 FutureTask 状态机制与 Future 的关系
FutureTask 是 Future 接口的具体实现类,同时也实现了 Runnable 接口,这使它既可以被 Thread 直接执行,也可以提交给 Executor。FutureTask 内部维护一个状态机,用于追踪任务执行状态:
- NEW:初始状态,任务尚未执行
- COMPLETING:任务已完成,结果正在设置
- NORMAL:任务正常完成
- EXCEPTIONAL:任务执行过程中抛出异常
- CANCELLED:任务被取消(未开始执行)
- INTERRUPTING:任务正在被中断
- INTERRUPTED:任务已被中断
3.3 FutureTask 与 Executor 关系
在实际应用中,FutureTask 通常与 ExecutorService 结合使用,而不是直接通过 Thread 启动:
ExecutorService executor = Executors.newFixedThreadPool(2);
// 方式1:使用submit提交Callable
Future<Integer> future1 = executor.submit(() -> {
Thread.sleep(1000);
return 123;
});
// 方式2:创建FutureTask然后提交
FutureTask<Integer> futureTask = new FutureTask<>(() -> {
Thread.sleep(1000);
return 456;
});
executor.submit(futureTask);
// 通过Future获取结果
Integer result1 = future1.get();
Integer result2 = futureTask.get();
3.4 Future 接口的关键方法
Future 接口提供了多个方法来管理异步任务:
get()
- 阻塞获取结果,直到任务完成get(long timeout, TimeUnit unit)
- 带超时的阻塞获取cancel(boolean mayInterruptIfRunning)
- 尝试取消任务isCancelled()
- 检查任务是否被取消isDone()
- 检查任务是否完成
四、使用 Executor 框架创建线程
4.1 基本原理
Executor 框架是 Java 5 引入的,它提供了一套高级的线程池管理 API,实现了线程与任务的解耦,并对创建和管理线程提供了规范化的控制。
import java.util.concurrent.*;
public class ExecutorDemo {
public static void main(String[] args) {
// 创建固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
// 提交Runnable任务
for (int i = 0; i < 5; i++) {
final int taskId = i;
executor.execute(() -> {
String threadName = Thread.currentThread().getName();
System.out.println("任务" + taskId + "开始执行,线程名:" + threadName);
try {
// 模拟任务执行
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("任务" + taskId + "执行完成,线程名:" + threadName);
});
}
// 提交Callable任务并获取Future
Future<Integer> future = executor.submit(() -> {
System.out.println("计算任务开始执行,线程名:" + Thread.currentThread().getName());
Thread.sleep(2000);
return 123;
});
try {
System.out.println("计算结果:" + future.get());
} catch (Exception e) {
e.printStackTrace();
}
// 关闭线程池
executor.shutdown();
// 等待线程池终止(生产环境需要更优雅的处理)
try {
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
}
}
4.2 ThreadPoolExecutor 核心参数详解
ThreadPoolExecutor 是线程池的核心实现类,它有 7 个关键参数:
corePoolSize:核心线程数
- 线程池中长期保持的线程数量
- CPU 密集型任务建议设为:
Runtime.getRuntime().availableProcessors()
- IO 密集型任务建议设为:
Runtime.getRuntime().availableProcessors() * 2
或更高
maximumPoolSize:最大线程数
- 线程池允许创建的最大线程数
- 当工作队列满时,会创建临时线程直到达到最大线程数
keepAliveTime:线程存活时间
- 临时线程(超过核心线程数的线程)的空闲存活时间
workQueue:工作队列
- ArrayBlockingQueue:有界队列,容量固定,适合任务数量可预测的场景
- LinkedBlockingQueue:默认无界队列,可能导致内存溢出,需谨慎使用
- SynchronousQueue:无容量队列,提交任务必须有线程立即处理
- PriorityBlockingQueue:优先级队列,可对任务排序
threadFactory:线程工厂
- 用于创建和定制化线程(如命名、设置优先级等)
rejectedExecutionHandler:拒绝策略
- AbortPolicy:默认策略,拒绝任务时抛出 RejectedExecutionException
- CallerRunsPolicy:由提交任务的线程自己执行该任务(适合主线程提交任务的突发流量场景,自我调节)
- DiscardPolicy:直接丢弃任务,不抛出异常(适合允许丢弃任务的场景)
- DiscardOldestPolicy:丢弃队列头部(最旧)的任务,然后尝试执行当前任务(适合新任务优先的场景)
allowCoreThreadTimeOut:是否允许核心线程超时
- 默认 false,设为 true 时核心线程也会受 keepAliveTime 限制
4.3 任务类型与线程池参数调优
不同类型的任务需要不同的线程池配置:
// CPU密集型任务线程池(假设8核CPU)
int cpuCores = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor cpuIntensivePool = new ThreadPoolExecutor(
cpuCores, // 核心线程等于CPU核心数
cpuCores, // 最大线程等于CPU核心数
0L, TimeUnit.MILLISECONDS, // 非核心线程立即回收
new LinkedBlockingQueue<>(1000), // 有界队列避免OOM
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()); // 主线程执行,起到反馈调节作用
// IO密集型任务线程池(假设任务平均IO等待时间占比80%)
// 最佳线程数 = 核心数 / (1 - IO阻塞时间占比)
int optimalThreads = (int)(cpuCores / (1 - 0.8));
ThreadPoolExecutor ioIntensivePool = new ThreadPoolExecutor(
optimalThreads, // 核心线程设为最佳线程数
optimalThreads * 2, // 最大线程数可以更大
60L, TimeUnit.SECONDS, // 允许空闲线程存活一段时间
new ArrayBlockingQueue<>(5000), // 有界队列
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
// 短时任务线程池(执行时间短,提交频率高)
ThreadPoolExecutor shortTaskPool = new ThreadPoolExecutor(
10, // 保持一定数量的核心线程
200, // 允许更多临时线程
10L, TimeUnit.SECONDS, // 临时线程较快回收
new SynchronousQueue<>(), // 直接传递队列,无队列缓冲
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()); // 拒绝策略可视情况选择
4.4 常见的线程池类型及隐患
FixedThreadPool:固定大小线程池
ExecutorService fixedPool = Executors.newFixedThreadPool(5);
隐患:使用无界队列 LinkedBlockingQueue,队列可能无限增长导致 OOM
CachedThreadPool:可缓存线程池,适合短期异步任务
ExecutorService cachedPool = Executors.newCachedThreadPool();
隐患:最大线程数为 Integer.MAX_VALUE,可能创建大量线程导致资源耗尽
SingleThreadExecutor:单线程执行器,适合顺序执行任务
ExecutorService singlePool = Executors.newSingleThreadExecutor();
隐患:同样使用无界队列,可能堆积大量任务
ScheduledThreadPoolExecutor:支持定时和周期任务
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(3); // 延迟执行 scheduledPool.schedule(() -> System.out.println("延迟3秒执行"), 3, TimeUnit.SECONDS); // 周期执行(固定速率) scheduledPool.scheduleAtFixedRate(() -> System.out.println("每2秒执行一次"), 0, 2, TimeUnit.SECONDS); // 周期执行(固定延迟) scheduledPool.scheduleWithFixedDelay(() -> System.out.println("上一次执行完成后延迟1秒再执行"), 0, 1, TimeUnit.SECONDS);
4.5 拒绝策略详解及应用场景
AbortPolicy(默认):
new ThreadPoolExecutor.AbortPolicy()
- 行为:直接抛出 RejectedExecutionException 异常
- 适用场景:任务必须执行成功,且需要立即感知失败的情况
- 实例:订单处理系统,拒绝后可立即向用户反馈"系统繁忙"
CallerRunsPolicy:
new ThreadPoolExecutor.CallerRunsPolicy()
- 行为:将任务回退给提交任务的线程执行
- 适用场景:主线程提交任务,可接受主线程暂时阻塞的情况
- 实例:Web 服务器主线程提交请求处理任务,当线程池满时主线程自己处理,减缓请求接收速度,形成反馈调节
DiscardPolicy:
new ThreadPoolExecutor.DiscardPolicy()
- 行为:静默丢弃任务,不做任何处理
- 适用场景:任务可丢弃,且不需要通知
- 实例:日志记录、监控数据收集等非关键任务
DiscardOldestPolicy:
new ThreadPoolExecutor.DiscardOldestPolicy()
- 行为:丢弃队列头部(等待最久)的任务,然后尝试执行当前任务
- 适用场景:新任务比旧任务重要的场景
- 实例:实时数据处理,新数据比旧数据更有价值
自定义拒绝策略:
new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { // 自定义处理逻辑,如记录日志后丢弃 logger.warn("任务被拒绝: " + r.toString()); // 或放入备用队列 backupQueue.offer(r); } }
4.6 自定义线程池
生产环境中,应避免直接使用 Executors 工厂方法,而是自定义 ThreadPoolExecutor:
import java.util.concurrent.*;
public class CustomThreadPoolDemo {
public static void main(String[] args) {
// 创建自定义线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // 核心线程数
5, // 最大线程数
60, TimeUnit.SECONDS, // 空闲线程存活时间
new ArrayBlockingQueue<>(10), // 有界工作队列
new ThreadFactory() { // 自定义线程工厂
private int count = 0;
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("CustomThread-" + count++);
return t;
}
},
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:调用者运行
);
// 监控线程池状态
ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor();
monitor.scheduleAtFixedRate(() -> {
System.out.println("=== 线程池状态 ===");
System.out.println("活跃线程数: " + executor.getActiveCount());
System.out.println("核心线程数: " + executor.getCorePoolSize());
System.out.println("最大线程数: " + executor.getMaximumPoolSize());
System.out.println("线程池大小: " + executor.getPoolSize());
System.out.println("队列任务数: " + executor.getQueue().size());
System.out.println("================");
}, 0, 5, TimeUnit.SECONDS);
// 提交任务
for (int i = 0; i < 15; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("任务" + taskId + "由线程" +
Thread.currentThread().getName() + "执行");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// 关闭线程池
executor.shutdown();
monitor.shutdown();
}
}
4.7 优雅关闭线程池
在生产环境中,关闭线程池需要更加谨慎:
public static void shutdownPoolGracefully(ExecutorService pool) {
pool.shutdown(); // 停止接收新任务
try {
// 等待已提交任务执行完毕
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
// 取消当前执行的任务
pool.shutdownNow();
// 等待任务响应中断
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
System.err.println("线程池未能完全关闭");
}
}
} catch (InterruptedException ie) {
// 重新取消当前线程的所有任务
pool.shutdownNow();
// 保留中断状态
Thread.currentThread().interrupt();
}
}
五、线程异常处理对比
各种创建线程方式处理异常的方式有所不同:
5.1 Thread 和 Runnable 的异常处理
public class ThreadExceptionDemo {
public static void main(String[] args) {
// 设置默认未捕获异常处理器
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
System.out.println("线程" + t.getName() + "发生异常:" + e.getMessage());
});
// 创建线程并设置线程专属的异常处理器
Thread thread = new Thread(() -> {
throw new RuntimeException("测试异常");
}, "ExceptionThread");
thread.setUncaughtExceptionHandler((t, e) -> {
System.out.println("线程" + t.getName() + "的专属处理器捕获异常:" + e.getMessage());
});
thread.start();
}
}
5.2 Future 异常处理
public class FutureExceptionDemo {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(() -> {
throw new RuntimeException("Callable异常测试");
});
try {
Integer result = future.get();
} catch (InterruptedException e) {
System.out.println("任务被中断");
} catch (ExecutionException e) {
System.out.println("任务执行异常:" + e.getCause().getMessage());
}
executor.shutdown();
}
}
六、四种方式全面对比
下面通过表格对比这四种创建线程的方式:
创建方式 | 支持返回值 | 线程复用 | 异常处理方式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|---|---|
Thread 类 | 否 | 否 | UncaughtExceptionHandler | 简单直观 | 无法继承其他类;无法重用线程 | 简单场景,学习入门 |
Runnable 接口 | 否 | 否 | UncaughtExceptionHandler | 可以继承其他类;多个线程可共享一个任务 | 无法直接获取返回值 | 任务线程分离,无返回值场景 |
Callable+Future | 是 | 否 | Future.get()捕获 ExecutionException | 可获取返回值;可抛出受检异常;支持取消和超时 | 使用较复杂;获取结果阻塞 | 需要获取任务执行结果的场景 |
Executor 框架 | 是(Callable) | 是(线程池) | 线程池拒绝策略;Future 处理 | 线程池复用;任务调度灵活;资源管理优化 | 创建配置较复杂 | 生产环境,高并发场景 |
七、实际应用场景分析
7.1 Web 应用中的异步处理
@RestController
public class AsyncProcessController {
private final ExecutorService executor = Executors.newFixedThreadPool(10);
// 线程安全的结果存储
private static final ConcurrentHashMap<String, Future<Result>> resultStorage =
new ConcurrentHashMap<>();
@PostMapping("/process")
public String processRequest(@RequestBody Request request) {
// 提交异步任务并返回标识
Future<Result> future = executor.submit(() -> {
// 模拟耗时处理
Thread.sleep(5000);
return new Result("处理完成: " + request.getData());
});
// 将Future存储,供后续查询
resultStorage.put(request.getId(), future);
return "请求已提交,ID: " + request.getId();
}
@GetMapping("/result/{id}")
public String getResult(@PathVariable String id) {
Future<Result> future = resultStorage.get(id);
if (future == null) {
return "未找到对应请求";
}
if (future.isDone()) {
try {
Result result = future.get();
// 任务完成后从存储中移除,避免内存泄漏
resultStorage.remove(id);
return "处理结果: " + result.getMessage();
} catch (Exception e) {
return "处理出错: " + e.getMessage();
}
} else {
return "处理中...";
}
}
// 应用关闭时
@PreDestroy
public void destroy() {
shutdownPoolGracefully(executor);
}
}
7.2 并行计算性能对比
以下是一个简单的性能对比测试:
public class ThreadPerformanceCompare {
private static final int TASK_COUNT = 10000;
private static final int THREAD_COUNT = 100;
public static void main(String[] args) throws Exception {
// 测试直接创建线程
long start1 = System.currentTimeMillis();
testDirectThreads();
long time1 = System.currentTimeMillis() - start1;
// 测试线程池
long start2 = System.currentTimeMillis();
testThreadPool();
long time2 = System.currentTimeMillis() - start2;
System.out.println("直接创建线程耗时:" + time1 + "ms");
System.out.println("使用线程池耗时:" + time2 + "ms");
System.out.println("性能提升:" + (time1 - time2) * 100.0 / time1 + "%");
}
private static void testDirectThreads() throws Exception {
final CountDownLatch latch = new CountDownLatch(TASK_COUNT);
for (int i = 0; i < TASK_COUNT; i++) {
new Thread(() -> {
try {
// 模拟任务执行
Thread.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown();
}
}).start();
}
latch.await();
}
private static void testThreadPool() throws Exception {
final CountDownLatch latch = new CountDownLatch(TASK_COUNT);
// 线程池核心线程数设为THREAD_COUNT
ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
for (int i = 0; i < TASK_COUNT; i++) {
executor.execute(() -> {
try {
// 模拟任务执行
Thread.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown();
}
});
}
latch.await();
executor.shutdown();
}
}
性能差异说明:
- 线程池的性能优势主要来自于避免了线程频繁创建和销毁的开销
- 每创建一个线程大约需要 1MB 的内存空间,且线程上下文切换也有开销
- 本测试使用简单任务(sleep 1ms),这种情况下线程创建开销比任务执行开销大得多,性能差异被放大
- 实际生产中,应根据任务复杂度和执行时间特点进行专门的性能测试,并据此调整线程池参数
总结
本文深入剖析了 Java 中创建线程的四种方式:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用 Executor 框架。通过实例代码、源码解析和性能对比,我们可以得出以下几点结论:
- 简单任务:对于简单场景,Runnable 接口配合 Lambda 表达式是最简洁的方式
- 需要返回值:使用 Callable+Future 可以获取异步计算结果
- 生产环境:几乎所有生产环境都应该使用 Executor 框架管理线程资源
- 异常处理:不同方式的异常处理机制不同,需要针对性设计
- 性能考虑:线程创建和销毁开销大,池化复用能显著提升性能
在实际开发中,推荐遵循以下原则:
- 优先使用高级并发工具而非直接操作线程
- 自定义线程池参数,避免使用 Executors 工厂方法的隐患
- 根据任务特性(CPU 密集型/IO 密集型)选择合适的线程池大小
- 使用有界队列防止内存溢出,配合合理的拒绝策略
- 妥善处理线程异常,防止任务静默失败
- 优雅关闭线程池,避免资源泄露
掌握这四种线程创建方式及其适用场景,是 Java 多线程编程的重要基础,也是构建高性能并发应用的第一步。
在下一篇文章中,我们将深入探讨线程生命周期与基础操作。敬请期待!
感谢您耐心阅读到这里!如果觉得本文对您有帮助,欢迎点赞 👍、收藏 ⭐、分享给需要的朋友,您的支持是我持续输出技术干货的最大动力!
如果想获取更多 Java 技术深度解析,欢迎点击头像关注我,后续会每日更新高质量技术文章,陪您一起进阶成长~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。