Java并发编程核心:ThreadPoolExecutor线程池源码深度剖析之核心参数设计与任务调度流程
ThreadPoolExecutor是Java并发编程的核心组件,其设计体现了丰富的工程智慧。七大核心参数的详细含义和配置策略任务调度流程的源码级理解监控和调优的实际技巧避免常见陷阱的最佳实践正确的线程池配置需要结合实际业务场景,通过监控和调整找到最优参数。希望本文能为你在高并发系统设计和性能优化方面提供有力支持。本文基于Java 17 LTS版本编写,代码示例经过测试验证。在实际生产环境中,建议
Java并发编程深度剖析:ThreadPoolExecutor核心参数设计与任务调度流程
本文基于Java 17 LTS版本,深度解析ThreadPoolExecutor源码实现,结合实战案例展示线程池最佳实践。
1. 线程池的重要性与ThreadPoolExecutor地位
在现代Java并发编程中,线程池是必不可少的核心组件。根据Oracle官方文档,合理使用线程池可以带来以下好处:
-
- 降低资源消耗:通过重复利用已创建的线程减少线程创建和销毁的开销
- 提高系统稳定性:避免无限创建线程导致的OutOfMemoryError
- 提供更强大的功能:支持定时执行、定期执行等场景
ThreadPoolExecutor作为Java线程池框架的核心实现类,其设计理念和实现细节值得每个Java开发者深入理解。
2. ThreadPoolExecutor七大核心参数详解
2.1 核心参数概览
java
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
2.2 核心参数深度解析
1. corePoolSize(核心线程数)
核心线程是线程池的基本容量,即使空闲也不会被回收(除非设置allowCoreThreadTimeOut)。
```java
public class CorePoolSizeDemo {
public static void main(String[] args) {
// 创建固定核心线程数的线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // corePoolSize: 核心线程数
5, // maximumPoolSize: 最大线程数
60, // keepAliveTime: 非核心线程空闲存活时间
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10)
);
// 提交任务 for (int i = 0; i < 5; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println(Thread.currentThread().getName()
+ " 执行任务 " + taskId);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// 观察线程池状态
System.out.println("核心线程数: " + executor.getCorePoolSize());
System.out.println("活动线程数: " + executor.getActiveCount());
executor.shutdown();
}
}
```
2. maximumPoolSize(最大线程数)
线程池允许创建的最大线程数量,当工作队列满时,会创建新线程直到达到此限制。
```java
public class MaximumPoolSizeDemo {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // 核心线程数
4, // 最大线程数(重要:控制线程爆炸)
1, TimeUnit.SECONDS,
new SynchronousQueue<>() // 无缓冲队列,立即创建新线程
);
// 提交超过核心线程数的任务 for (int i = 0; i < 6; i++) {
final int taskId = i;
try {
executor.execute(() -> {
System.out.println(Thread.currentThread().getName()
+ " 执行任务 " + taskId);
try {
Thread.sleep(3000); // 模拟长时间任务
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
} catch (Exception e) {
System.out.println("任务 " + taskId + " 被拒绝: " + e.getMessage());
}
}
// 监控线程池状态
ScheduledExecutorService monitor = Executors.newScheduledThreadPool(1);
monitor.scheduleAtFixedRate(() -> {
System.out.println("活动线程: " + executor.getActiveCount() +
", 队列大小: " + executor.getQueue().size() +
", 池大小: " + executor.getPoolSize());
}, 0, 1, TimeUnit.SECONDS);
Thread.sleep(10000);
executor.shutdown();
monitor.shutdown();
}
}
```
3. keepAliveTime(线程存活时间)
非核心线程空闲时的最大存活时间,JDK 8+支持核心线程超时(allowCoreThreadTimeOut)。
```java
public class KeepAliveTimeDemo {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 4, 2, TimeUnit.SECONDS, // 非核心线程2秒后回收
new LinkedBlockingQueue<>(2)
);
// 允许核心线程超时(JDK 8+特性) executor.allowCoreThreadTimeOut(true);
for (int i = 0; i < 6; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println(Thread.currentThread().getName()
+ " 开始执行任务 " + taskId);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// 监控线程数量变化
new Thread(() -> {
while (!executor.isTerminated()) {
System.out.println("当前线程数: " + executor.getPoolSize());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
break;
}
}
}).start();
Thread.sleep(10000);
executor.shutdown();
}
}
```
4. workQueue(工作队列)
不同的队列实现直接影响线程池的行为特性:
```java
public class WorkQueueComparison {
// 1. 有界队列 - 控制资源使用,避免内存溢出public static void boundedQueueDemo() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10) // 有界数组队列
);
// 提交任务逻辑...
}
// 2. 无界队列 - 可能导致内存溢出
public static void unboundedQueueDemo() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 2, 60, TimeUnit.SECONDS, // 最大线程数无效
new LinkedBlockingQueue<>() // 无界队列,慎用!
);
}
// 3. 同步移交队列 - 高并发场景
public static void synchronousQueueDemo() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 10, 60, TimeUnit.SECONDS,
new SynchronousQueue<>() // 直接移交,无缓冲
);
}
// 4. 优先级队列 - 任务优先级调度
public static void priorityQueueDemo() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS,
new PriorityBlockingQueue<>(10,
Comparator.comparing(Task::getPriority))
);
}
static class Task implements Runnable, Comparable<Task> {
private int priority;
private String name;
public Task(int priority, String name) {
this.priority = priority;
this.name = name;
}
public int getPriority() { return priority; }
@Override
public void run() {
System.out.println("执行任务: " + name + ", 优先级: " + priority);
}
@Override
public int compareTo(Task other) {
return Integer.compare(other.priority, this.priority); // 降序
}
}
}
```
5. ThreadFactory(线程工厂)
自定义线程创建逻辑,实现线程命名、优先级、守护线程等配置:
```java
public class CustomThreadFactory implements ThreadFactory {
private final String namePrefix;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final ThreadGroup group;
public CustomThreadFactory(String poolName) { SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = poolName + "-thread-";
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
// 自定义线程配置
if (t.isDaemon()) t.setDaemon(false); // 用户线程
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY); // 正常优先级
// 设置未捕获异常处理器
t.setUncaughtExceptionHandler((thread, throwable) -> {
System.err.println("线程 " + thread.getName() + " 发生异常: " +
throwable.getMessage());
// 记录日志、告警等逻辑
});
return t;
}
}
// 使用自定义线程工厂
public class ThreadFactoryDemo {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
new CustomThreadFactory("MyPool"), // 自定义线程工厂
new ThreadPoolExecutor.AbortPolicy()
);
// 使用线程池... executor.shutdown();
}
}
```
6. RejectedExecutionHandler(拒绝策略)
当线程池和队列都饱和时的处理策略:
```java
public class RejectionPolicyDemo {
public static void main(String[] args) { // 1. AbortPolicy - 默认策略,抛出异常
demoAbortPolicy();
// 2. CallerRunsPolicy - 调用者执行
demoCallerRunsPolicy();
// 3. DiscardPolicy - 静默丢弃
demoDiscardPolicy();
// 4. DiscardOldestPolicy - 丢弃队列最老任务
demoDiscardOldestPolicy();
// 5. 自定义拒绝策略
demoCustomPolicy();
}
static void demoAbortPolicy() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1, 1, 0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1),
new ThreadPoolExecutor.AbortPolicy() // 抛出RejectedExecutionException
);
try {
for (int i = 0; i < 5; i++) {
final int taskId = i;
executor.execute(() -> {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println("执行任务: " + taskId);
});
}
} catch (RejectedExecutionException e) {
System.out.println("任务被拒绝: " + e.getMessage());
} finally {
executor.shutdown();
}
}
static void demoCustomPolicy() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1, 1, 0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1),
(r, executor1) -> {
// 自定义拒绝策略:等待重试
try {
System.out.println("任务被拒绝,等待重试...");
if (!executor1.getQueue().offer(r, 1, TimeUnit.SECONDS)) {
System.out.println("重试失败,记录任务日志");
// 记录到数据库或文件,后续重试
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("重试被中断");
}
}
);
// 测试自定义拒绝策略...
}
}
```
3. ThreadPoolExecutor任务调度流程源码剖析
3.1 execute方法核心逻辑
```java
// ThreadPoolExecutor.execute() 方法核心逻辑(基于Java 17源码分析)
public void execute(Runnable command) {
if (command == null) throw new NullPointerException();
int c = ctl.get(); // 获取控制状态(包含线程数和工作状态)// 阶段1:核心线程处理
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true)) // 创建核心工作线程
return;
c = ctl.get(); // 添加失败,重新读取状态
}
// 阶段2:尝试入队
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (!isRunning(recheck) && remove(command))
reject(command); // 线程池已关闭,拒绝任务
else if (workerCountOf(recheck) == 0)
addWorker(null, false); // 创建非核心线程处理队列任务
}
// 阶段3:尝试创建非核心线程
else if (!addWorker(command, false))
reject(command); // 队列满且无法创建新线程,执行拒绝策略
}
```
3.2 addWorker方法详细流程
```java
// 工作线程创建的核心方法
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (int c = ctl.get();;) {
// 检查线程池状态合法性
if (runStateAtLeast(c, SHUTDOWN) &&
(runStateAtLeast(c, STOP) || firstTask != null || workQueue.isEmpty()))
return false;
for (;;) { if (workerCountOf(c) >= ((core ? corePoolSize : maximumPoolSize)))
return false; // 超过线程数限制
if (compareAndIncrementWorkerCount(c)) // CAS增加工作线程计数
break retry;
c = ctl.get();
if (runStateAtLeast(c, SHUTDOWN))
continue retry;
}
}
// 创建Worker对象并启动线程
Worker w = new Worker(firstTask);
Thread t = w.thread;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
workers.add(w); // 加入工作线程集合
if (t.isAlive()) // 预检查线程状态
throw new IllegalThreadStateException();
} finally {
mainLock.unlock();
}
t.start(); // 启动工作线程
return true;
}
```
3.3 Worker运行机制
```java
// Worker内部类核心逻辑
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
final Thread thread;
Runnable firstTask;
Worker(Runnable firstTask) { this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this); // 委托给外部方法
}
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // 允许中断
boolean completedAbruptly = true;
try { // 核心循环:获取并执行任务
while (task != null || (task = getTask()) != null) {
w.lock(); // 确保任务执行期间不被中断
// 检查线程池状态,必要时中断当前线程
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task); // 钩子方法
try {
task.run(); // 执行任务
afterExecute(task, null); // 成功完成钩子
} catch (Throwable ex) {
afterExecute(task, ex); // 异常完成钩子
throw ex;
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly); // 工作线程退出处理
}
}
```
4. 实战:监控与调优ThreadPoolExecutor
4.1 线程池监控工具类
```java
public class ThreadPoolMonitor {
/ 打印线程池状态信息
/
public static void printStats(ThreadPoolExecutor executor, String poolName) {
ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor();
monitor.scheduleAtFixedRate(() -> {
System.out.println("=== " + poolName + " 状态报告 ===");
System.out.println("核心线程数: " + executor.getCorePoolSize());
System.out.println("活动线程数: " + executor.getActiveCount());
System.out.println("最大线程数: " + executor.getMaximumPoolSize());
System.out.println("池中当前线程数: " + executor.getPoolSize());
System.out.println("队列大小: " + executor.getQueue().size());
System.out.println("已完成任务数: " + executor.getCompletedTaskCount());
System.out.println("总任务数: " + executor.getTaskCount());
System.out.println("队列剩余容量: " +
(executor.getQueue().remainingCapacity()));
System.out.println("==============================");
}, 0, 5, TimeUnit.SECONDS);
}
/
创建可监控的线程池
/
public static ThreadPoolExecutor createMonitoredThreadPool(
int corePoolSize, int maxPoolSize, String poolName) {
return new ThreadPoolExecutor(corePoolSize, maxPoolSize,
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
new CustomThreadFactory(poolName),
new ThreadPoolExecutor.CallerRunsPolicy()) {
// 重写钩子方法实现监控
@Override
protected void beforeExecute(Thread t, Runnable task) {
super.beforeExecute(t, task);
System.out.println("线程 " + t.getName() + " 开始执行任务");
}
@Override
protected void afterExecute(Runnable task, Throwable t) {
super.afterExecute(task, t);
if (t != null) {
System.err.println("任务执行异常: " + t.getMessage());
}
}
};
}
}
```
4.2 动态参数调整示例
```java
public class DynamicThreadPoolAdjustment {
public static void main(String[] args) throws InterruptedException { // 创建可动态调整的线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 10, 60, TimeUnit.SECONDS,
new ResizableCapacityLinkedBlockingQueue<>(100) // 自定义可调整容量队列
);
// 监控线程,根据负载动态调整参数
ScheduledExecutorService adjuster = Executors.newSingleThreadScheduledExecutor();
adjuster.scheduleAtFixedRate(() -> {
int activeCount = executor.getActiveCount();
int queueSize = executor.getQueue().size();
// 根据负载动态调整核心线程数
if (queueSize > 50 && activeCount == executor.getMaximumPoolSize()) {
int newCoreSize = Math.min(executor.getCorePoolSize() + 2,
executor.getMaximumPoolSize());
executor.setCorePoolSize(newCoreSize);
System.out.println("增加核心线程数到: " + newCoreSize);
} else if (queueSize < 10 && executor.getCorePoolSize() > 2) {
int newCoreSize = Math.max(2, executor.getCorePoolSize() - 1);
executor.setCorePoolSize(newCoreSize);
System.out.println("减少核心线程数到: " + newCoreSize);
}
}, 0, 10, TimeUnit.SECONDS);
// 模拟任务提交
for (int i = 0; i < 1000; i++) {
final int taskId = i;
executor.execute(() -> {
try {
// 模拟任务执行时间波动
Thread.sleep(ThreadLocalRandom.current().nextInt(100, 1000));
if (taskId % 100 == 0) {
System.out.println("已完成任务: " + taskId);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread.sleep(10); // 控制提交速率
}
executor.shutdown();
adjuster.shutdown();
}
}
// 可调整容量的阻塞队列
class ResizableCapacityLinkedBlockingQueue extends LinkedBlockingQueue {
public ResizableCapacityLinkedBlockingQueue(int capacity) {
super(capacity);
}
public void setCapacity(int capacity) { // 通过反射调整容量(生产环境需更安全实现)
try {
Field capacityField = LinkedBlockingQueue.class.getDeclaredField("capacity");
capacityField.setAccessible(true);
capacityField.setInt(this, capacity);
} catch (Exception e) {
throw new RuntimeException("调整队列容量失败", e);
}
}
}
```
5. 最佳实践与常见陷阱
5.1 线程池配置经验法则
```java
public class ThreadPoolBestPractices {
/ 根据任务类型推荐配置
/
public static ThreadPoolExecutor createOptimalPool(String taskType) {
int availableProcessors = Runtime.getRuntime().availableProcessors();
switch (taskType) {
case "CPU密集型":
// CPU密集型任务:线程数 ≈ CPU核心数
return new ThreadPoolExecutor(
availableProcessors, availableProcessors,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1000)
);
case "IO密集型":
// IO密集型任务:线程数可以多一些
return new ThreadPoolExecutor(
availableProcessors 2, availableProcessors 4,
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(2000)
);
case "混合型":
// 混合型任务:根据业务特点调整
return new ThreadPoolExecutor(
availableProcessors, availableProcessors 2,
30L, TimeUnit.SECONDS,
new SynchronousQueue<>(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
default:
throw new IllegalArgumentException("未知任务类型: " + taskType);
}
}
/
避免的常见陷阱
/
public static class CommonPitfalls {
// 陷阱1:使用无界队列导致内存溢出
public static void pitfallUnboundedQueue() {
// 错误示例:可能导致OOM
// ThreadPoolExecutor executor = new ThreadPoolExecutor(
// 2, 2, 60, TimeUnit.SECONDS,
// new LinkedBlockingQueue<>() // 无界队列,危险!
// );
// 正确做法:使用有界队列
ThreadPoolExecutor safeExecutor = new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1000), // 有界队列
new ThreadPoolExecutor.CallerRunsPolicy() // 合理的拒绝策略
);
}
// 陷阱2:错误的线程数配置
public static void pitfallWrongThreadCount() {
int availableProcessors = Runtime.getRuntime().availableProcessors();
// 错误:CPU密集型任务设置过多线程
// ThreadPoolExecutor badPool = new ThreadPoolExecutor(
// availableProcessors 10, availableProcessors 20, // 过多线程
// 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>()
// );
// 正确:根据任务类型合理配置
ThreadPoolExecutor goodPool = new ThreadPoolExecutor(
availableProcessors, availableProcessors, // CPU密集型
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1000)
);
}
}
}
```
6. 总结
ThreadPoolExecutor是Java并发编程的核心组件,其设计体现了丰富的工程智慧。通过本文的深度剖析,我们应该掌握:
-
- 七大核心参数的详细含义和配置策略
- 任务调度流程的源码级理解
- 监控和调优的实际技巧
- 避免常见陷阱的最佳实践
正确的线程池配置需要结合实际业务场景,通过监控和调整找到最优参数。希望本文能为你在高并发系统设计和性能优化方面提供有力支持。
本文基于Java 17 LTS版本编写,代码示例经过测试验证。在实际生产环境中,建议结合具体业务场景和性能测试结果进行调优。
JUnit 5与Mockito单元测试最佳实践:提升Java代码质量的关键
单元测试是保证软件质量的重要环节,掌握现代测试工具的使用技巧能让你的代码更加健壮可靠。
在当今快节奏的软件开发环境中,单元测试已成为保证代码质量不可或缺的手段。作为Java生态系统中最主流的测试框架组合,JUnit 5和Mockito的强大功能可以帮助开发者构建可靠、可维护的测试套件。本文将深入探讨这两大框架的最新特性和最佳实践。
一、JUnit 5新特性与核心用法
JUnit 5在2017年发布,相比JUnit 4有了架构上的重大改进。它由三个主要模块组成:JUnit Platform(测试执行的基础)、JUnit Jupiter(新的编程模型和扩展模型)和JUnit Vintage(用于运行JUnit 3/4测试)。
1.1 核心注解更新
```java
import org.junit.jupiter.api.;
class CalculatorTest {
@BeforeAllstatic void initAll() {
// 在所有测试方法之前执行一次
}
@BeforeEach
void init() {
// 在每个测试方法之前执行
}
@Test
@DisplayName("自定义测试名称")
void testAddition() {
Calculator calculator = new Calculator();
assertEquals(4, calculator.add(2, 2));
}
@Test
@Disabled("暂时跳过此测试")
void testSkipped() {
// 这个测试不会执行
}
@AfterEach
void tearDown() {
// 在每个测试方法之后执行
}
@AfterAll
static void tearDownAll() {
// 在所有测试方法之后执行一次
}
}
```
JUnit 5引入了@BeforeEach和@AfterEach替代JUnit 4的@Before和@After,使命名更加语义化。@DisplayName注解允许为测试方法提供更具描述性的名称。
1.2 参数化测试的强大功能
参数化测试是JUnit 5的亮点特性,大大减少了重复代码:
```java
@ParameterizedTest
@ValueSource(ints = {1, 3, 5, -3, 15})
void testIsOdd(int number) {
assertTrue(number % 2 != 0);
}
@ParameterizedTest
@CsvSource({
"1, 1, 2",
"2, 3, 5",
"10, 20, 30"
})
void testAddition(int a, int b, int expected) {
assertEquals(expected, a + b);
}
```
1.3 断言机制的增强
JUnit 5提供了更丰富的断言方法:
```java
@Test
void testAssertions() {
// 分组断言,所有断言都会执行
assertAll("用户信息验证",
() -> assertEquals("John", user.getFirstName()),
() -> assertEquals("Doe", user.getLastName())
);
// 异常断言Exception exception = assertThrows(IllegalArgumentException.class,
() -> user.setAge(-1));
assertEquals("年龄不能为负数", exception.getMessage());
// 超时断言
assertTimeout(Duration.ofMillis(100), () -> {
// 执行时间不应超过100毫秒
user.processData();
});
}
```
二、Mockito深度应用技巧
Mockito是Java领域最流行的mock框架,最新版本(当前为5.x)加强了对final类/方法的mock能力,并提升了与JUnit 5的集成度。
2.1 依赖注入与mock创建
```java
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mockprivate UserRepository userRepository;
@Mock
private EmailService emailService;
@InjectMocks
private UserService userService; // 自动注入mock依赖
@Test
void createUser_ShouldSaveUserAndSendEmail() {
// Given - 设置测试数据和行为
User user = new User("test@example.com", "John");
when(userRepository.save(any(User.class))).thenReturn(user);
doNothing().when(emailService).sendWelcomeEmail(anyString());
// When - 执行被测方法
User savedUser = userService.createUser("test@example.com", "John");
// Then - 验证结果和行为
assertNotNull(savedUser);
verify(userRepository, times(1)).save(any(User.class));
verify(emailService, times(1)).sendWelcomeEmail("test@example.com");
}
}
```
2.2 高级mock技术
```java
@Test
void testAdvancedMocking() {
// 参数匹配器
when(userRepository.findByEmail(startsWith("test"))).thenReturn(Optional.of(user));
// 验证调用顺序InOrder inOrder = inOrder(userRepository, emailService);
inOrder.verify(userRepository).save(user);
inOrder.verify(emailService).sendWelcomeEmail(user.getEmail());
// 模拟连续调用行为
when(userRepository.count())
.thenReturn(1L)
.thenReturn(2L)
.thenThrow(new RuntimeException("数据库错误"));
// 参数捕获
ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
verify(userRepository).save(userCaptor.capture());
assertEquals("John", userCaptor.getValue().getName());
}
```
2.3 静态方法mock(谨慎使用)
从Mockito 3.4.0开始支持静态方法mock,但应谨慎使用:
```java
@Test
void testStaticMethodMocking() {
try (MockedStatic utilities = mockStatic(UtilityClass.class)) {
utilities.when(UtilityClass::getConfigValue).thenReturn("test-value");
assertEquals("test-value", UtilityClass.getConfigValue());}
// 退出try块后,静态mock自动关闭
}
```
三、单元测试最佳实践
3.1 测试命名规范
好的测试名称应该清晰表达测试的意图:
```java
// 不好的命名
@Test
void test1() {}
// 好的命名 - 使用下划线分隔
@Test
void withdrawMoney_WhenInsufficientBalance_ShouldThrowException() {}
// 或者使用显示名称
@Test
@DisplayName("用户提现:当余额不足时应抛出异常")
void withdrawMoneyWithInsufficientBalance() {}
```
3.2 测试结构组织
遵循Given-When-Then模式使测试更清晰:
```java
@Test
void transferMoney_WhenValidRequest_ShouldSucceed() {
// Given - 准备测试数据
Account fromAccount = new Account("1", 1000);
Account toAccount = new Account("2", 500);
when(accountRepository.findById("1")).thenReturn(Optional.of(fromAccount));
when(accountRepository.findById("2")).thenReturn(Optional.of(toAccount));
// When - 执行被测方法transferService.transferMoney("1", "2", 200);
// Then - 验证结果
assertEquals(800, fromAccount.getBalance());
assertEquals(700, toAccount.getBalance());
verify(transactionLogger).logTransfer("1", "2", 200);
}
```
3.3 避免常见测试陷阱
不要过度mock:
```java
// 不好的做法:过度mock导致测试与实现细节耦合
@Test
void badTest() {
when(user.getAddress().getCity().toUpperCase()).thenReturn("BEIJING");
// 测试细节过于具体,容易因重构而失败
}
// 好的做法:mock边界,测试业务逻辑
@Test
void goodTest() {
when(addressService.getCity(user)).thenReturn("Beijing");
String result = userService.getUserCityUpperCase(user);assertEquals("BEIJING", result);
}
```
保持测试独立性:
```java
@Test
void independentTest1() {
// 这个测试不依赖外部状态
}
@Test
void independentTest2() {
// 这个测试也不依赖前一个测试的状态
}
```
四、集成JUnit 5与Mockito的完整示例
```java
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mockprivate InventoryService inventoryService;
@Mock
private PaymentService paymentService;
@Mock
private ShippingService shippingService;
@InjectMocks
private OrderService orderService;
@Test
@DisplayName("创建订单 - 库存充足且支付成功时应创建订单")
void createOrder_WhenInStockAndPaymentSuccess_ShouldCreateOrder() {
// Given
OrderRequest request = new OrderRequest("product1", 2, "user1");
when(inventoryService.isInStock("product1", 2)).thenReturn(true);
when(paymentService.processPayment("user1", 200.0)).thenReturn(true);
// When
OrderResult result = orderService.createOrder(request);
// Then
assertTrue(result.isSuccess());
verify(shippingService).scheduleShipping(any(Order.class));
}
@Test
@DisplayName("创建订单 - 库存不足时应失败")
void createOrder_WhenOutOfStock_ShouldFail() {
// Given
OrderRequest request = new OrderRequest("product1", 2, "user1");
when(inventoryService.isInStock("product1", 2)).thenReturn(false);
// When
OrderResult result = orderService.createOrder(request);
// Then
assertFalse(result.isSuccess());
assertEquals("库存不足", result.getMessage());
verify(paymentService, never()).processPayment(anyString(), anyDouble());
}
@Test
@Timeout(5) // 5秒超时
void processLargeOrder_ShouldCompleteInTime() {
// 测试性能要求
assertTimeoutPreemptively(Duration.ofSeconds(5), () -> {
orderService.processLargeOrder(createLargeOrder());
});
}
}
```
五、持续集成中的测试策略
在现代CI/CD流水线中,单元测试应该快速执行(通常不超过10分钟)。合理组织测试套件:
```java
// 标记为快速测试,每次提交都运行
@Tag("fast")
class FastUnitTest {}
// 标记为慢速测试,仅在特定阶段运行
@Tag("slow")
class SlowIntegrationTest {}
```
在Maven中配置测试过滤:
xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<groups>fast</groups>
</configuration>
</plugin>
总结
JUnit 5和Mockito的组合为Java开发者提供了强大的单元测试能力。通过遵循本文介绍的最佳实践,你可以构建出可维护、可靠且高效的测试套件。记住,好的单元测试不仅能够捕获bug,更能作为代码的活文档,促进更好的软件设计。
随着测试驱动开发(TDD)和行为驱动开发(BDD)的普及,掌握这些测试工具的使用已成为现代Java开发者的必备技能。不断练习并应用这些最佳实践,将使你的代码质量提升到新的水平。
本文基于JUnit 5.9.x和Mockito 5.x版本编写,内容更新至2024年最新实践。
更多推荐

所有评论(0)