Junit测试类线程执行睡眠sleep()后次线程后面的程序不能进行

本文探讨了在JUnit测试环境中使用线程时遇到的问题,特别是当线程执行sleep()方法时,后续操作无法正常进行的现象。通过具体示例,展示了如何创建并启动两个线程,并观察到其中一个线程因sleep()而未输出预期结果。

Junit测试类线程执行睡眠sleep()后次线程后面的程序不能进行;因为junit执行的程序必须是激活状态的。而sleep是睡眠状态,一旦执行就会自动退出程序。

如下:

package com.xiaoyexinxin.ThreadLearn;

import org.junit.Test;

public class MultiThread {

	private int num=0;
	public synchronized void printNum(String tar){
		try {
			if(tar.equals("a")){
				num=100;
				System.out.println("线程a");
				Thread.sleep(10);//休息1秒,之所以这样是为了让大家看到两个线程互不干扰,如果不休息的话,瞬间执行完了,看不出效果  
			}else{
				num=200;
				System.out.println("线程b");
//				Thread.sleep(10);//休息1秒,之所以这样是为了让大家看到两个线程互不干扰,如果不休息的话,瞬间执行完了,看不出效果  

			}
			System.out.println("输出的线程是:"+tar+",num:"+num);
		}catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	@Test
	public void MultiThreadTest(){
		final MultiThread mThread1=new MultiThread();
		final MultiThread mThread2=new MultiThread();//设为final表示此对象不能别继承,没有子类。如果是方法被定义成final,则此方法不能被重写
		Thread t1=new Thread(new Runnable() {
			
			public void run() {
				// TODO Auto-generated method stub
				mThread1.printNum("a");
			}
		});
		Thread t2=new Thread(new Runnable() {
			
			public void run() {
				// TODO Auto-generated method stub
				mThread2.printNum("b");
				
			}
		});
		t1.start();
		t2.start();
	}
}

输出结果:

线程a
线程b
输出的线程是:b,num:200
可见上面有两个线程,但是没有
输出的线程是:a,num:200
输出。
<think> 我们之前讨论的是使用内存缓存和定时任务来每周更新一个值,并通过Controller返回给前端。现在用户希望将更新频率从7天改为30秒,并进行测试。 由于不使用数据库,我们仍然使用内存存储(如AtomicReference)和Spring的定时任务。修改定时任务的cron表达式或固定延迟时间即可。 但是,将7天改为30秒,我们需要注意: 1. 原本的cron表达式是每周执行,现在需要改为每30秒执行。 2. 30秒的间隔可以使用 fixedRate 或 fixedDelay,或者使用cron表达式(但cron对于秒级不太直观,这里用fixedRate更简单)。 3. 测试:我们可以编写一个测试用例,模拟时间流逝或者直接调用刷新方法,然后验证值是否按预期更新。 实现步骤: 1. 修改定时任务:将原来的每周执行改为每30秒执行。 - 使用 `@Scheduled(fixedRate = 30000)` 表示每30秒执行(注意单位是毫秒,所以30000毫秒=30秒) - 或者使用 `@Scheduled(cron = "0/30 * * * * ?")` 表示每30秒执行(从0秒开始,每30秒一) 2. 为了测试,我们可以暴露一个触发刷新的端点(可选,便于测试),或者编写单元测试来验证刷新逻辑。 3. 编写测试用例: - 使用Spring的测试框架,或者简单的JUnit测试(如果单独测试生成逻辑)。 - 在测试中,我们可以调用刷新方法,然后检查更新后的值。 注意:由于生产环境是每30秒更新一,在测试时我们不需要等待30秒,可以直接调用刷新方法。 代码调整示例: 原代码中的定时任务方法: ```java // 每周一 00:00 自动刷新值 @Scheduled(cron = "0 0 0 * * MON") public void refreshValue() { String newValue = generateWeeklyValue(); // 生成逻辑 weeklyValue.set(newValue); } ``` 改为: ```java // 每30秒刷新一 @Scheduled(fixedRate = 30000) public void refreshValue() { String newValue = generateValue(); // 生成逻辑,方法名可以改一下,因为不再是每周了 weeklyValue.set(newValue); } ``` 同时,为了便于测试,我们可以将生成逻辑单独提取出来,以便测试时可以调用。 测试用例(使用SpringBootTest): ```java @SpringBootTest public class WeeklyValueControllerTest { @Autowired private WeeklyValueController controller; @Test public void testRefreshValue() throws InterruptedException { // 初始值 String firstValue = controller.getWeeklyData(); // 等待30秒?但这样测试太慢,我们可以直接调用刷新方法 controller.refreshValue(); // 由于refreshValue是public的,我们可以直接调用 String secondValue = controller.getWeeklyData(); // 验证值是否改变(注意:如果生成逻辑是时间戳相关,那么两调用时间不同,值应该不同) assertNotEquals(firstValue, secondValue); // 或者,我们可以模拟时间?但这里直接调用方法刷新,已经可以验证刷新功能。 } } ``` 但是,如果生成逻辑是依赖于当前时间的,那么两调用间隔很短,可能时间戳相同?为了避免这种情况,我们可以在生成逻辑中加入更细粒度的时间(比如毫秒)或者使用随机数?但根据用户需求,生成逻辑可能是固定的,所以这里假设用户会修改生成逻辑。 另外,如果生成逻辑中使用了“周数”,那么改为30秒后,生成逻辑也需要调整。因为用户原本是每周生成一,现在每30秒生成一,所以生成逻辑可能不再基于周数,而是其他。 因此,我们需要根据实际需求调整generateValue方法。 总结: 1. 修改定时任务为每30秒执行(使用fixedRate=30000)。 2. 调整生成逻辑(如果需要),因为原来的生成逻辑可能是按周生成的,现在需要按更细粒度。 3. 编写测试用例,直接调用refreshValue方法验证更新是否成功。 注意:在测试中直接调用刷新方法,而不是等待30秒,这样测试快速且可靠。 生成逻辑调整示例: 原逻辑(按周生成): ```java private String generateWeeklyValue() { return "WEEKLY_" + System.currentTimeMillis() / (7 * 24 * 3600 * 1000L); } ``` 如果希望每30秒生成一个不同的值,可以使用秒或毫秒时间戳: ```java private String generateValue() { // 使用当前时间戳(毫秒) return "VALUE_" + System.currentTimeMillis(); } ``` 这样每30秒会更新一,但每更新都会是当前时间戳(所以每都会不同)。 如果希望每30秒生成的值在30秒内是固定的,那么这种方法可以。 测试用例可以验证值是否变化。 另外,为了确保应用启动时就有值,构造函数中调用一refreshValue。 完整代码示例: ```java import org.springframework.scheduling.annotation.Scheduled; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.concurrent.atomic.AtomicReference; @RestController public class WeeklyValueController { private final AtomicReference<String> currentValue = new AtomicReference<>(); public WeeklyValueController() { refreshValue(); // 初始化 } @Scheduled(fixedRate = 30000) // 每30秒执行 public void refreshValue() { String newValue = generateValue(); currentValue.set(newValue); } private String generateValue() { // 示例:使用当前时间戳 return "VAL_" + System.currentTimeMillis(); } @GetMapping("/current-value") public String getCurrentValue() { return currentValue.get(); } } ``` 测试代码: ```java import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest class WeeklyValueControllerTest { @Autowired WeeklyValueController controller; @Test void testValueRefresh() { // 获取初始值 String firstValue = controller.getCurrentValue(); // 等待一小段时间(小于30秒)然后刷新 // 为了测试刷新,我们直接调用刷新方法 controller.refreshValue(); String secondValue = controller.getCurrentValue(); // 两值应该不同 assertNotEquals(firstValue, secondValue); } } ``` 注意:如果两调用refreshValue()非常快,时间戳可能相同?因为System.currentTimeMillis()的最小单位是毫秒,而计算机执行速度很快,有可能在同一个毫秒内完成两调用。为了避免测试失败,我们可以在两调用之间等待1毫秒,或者修改生成逻辑确保每调用都不同(比如使用随机数+时间戳,或者使用AtomicLong生成序列号)。但是,在30秒的间隔下,生产环境不会出现这种情况。在测试中,我们可以考虑: 1. 在测试中等待1毫秒: ```java @Test void testValueRefresh() throws InterruptedException { String firstValue = controller.getCurrentValue(); Thread.sleep(1); // 睡眠1毫秒,确保时间戳变化 controller.refreshValue(); String secondValue = controller.getCurrentValue(); assertNotEquals(firstValue, secondValue); } ``` 2. 或者,在生成逻辑中加入一个自增序列(仅用于测试)?但这样会改变生产代码。 3. 或者,我们允许测试中两刷新在同一毫秒内,那么值相同,测试就会失败。为了避免这种情况,我们可以使用更精确的时间(如System.nanoTime())或者使用UUID?但是,根据需求,每30秒更新一,同一个30秒内值相同,所以测试中我们连续调用两刷新,其实模拟的是两个30秒的周期,所以应该期望值不同。因此,我们可以在测试中加入等待,确保时间戳变化。 考虑到测试的可靠性,我们选择在两刷新之间加入短暂等待(1毫秒),以确保时间戳变化。 如果用户对生成逻辑有特定要求,可以根据实际情况调整。 相关问题: 1. 如何调整定时任务的执行频率? 2. 如何确保定时任务在分布式环境下的正确执行? 3. 如何测试定时任务? 4. 生成逻辑中如何保证线程安全? 5. 如果生成逻辑需要较长时间(超过30秒),定时任务会重叠执行吗?如何避免? 注意:如果生成逻辑执行时间超过30秒,那么由于fixedRate是固定频率,下一任务会立即开始(在上一任务开始后30秒),这可能导致线程池不够用(任务堆积)。为了避免,可以考虑使用fixedDelay,它会在上一任务结束后开始计算间隔。 所以,如果生成逻辑执行时间不确定,建议使用fixedDelay: ```java @Scheduled(fixedDelay = 30000) // 每任务结束后,间隔30秒再执行下一 ``` 这样不会出现任务重叠。 但根据需求,生成逻辑应该是很快的(直接返回一个值),所以使用fixedRate即可。 以上。 </think>### 解决方案:30秒间隔的内存缓存刷新方案 #### 1. **核心实现代码** ```java import org.springframework.scheduling.annotation.Scheduled; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @RestController public class ValueController { // 值存储(线程安全) private final AtomicReference<String> cachedValue = new AtomicReference<>(); // 刷新计数器(用于验证) private final AtomicLong refreshCount = new AtomicLong(0); public ValueController() { refreshValue(); // 初始化时立即生成值 } // 每30秒刷新一(fixedRate单位:毫秒) @Scheduled(fixedRate = 30_000) public void refreshValue() { String newValue = generateValue(); cachedValue.set(newValue); refreshCount.incrementAndGet(); // 计数器+1 } // 值生成逻辑(示例) private String generateValue() { return "Value_" + System.currentTimeMillis() + "_v" + refreshCount.get(); } @GetMapping("/dynamic-value") public String getDynamicValue() { return cachedValue.get(); } } ``` #### 2. **测试验证方案** ##### 方法1:JUnit单元测试(直接验证刷新逻辑) ```java @SpringBootTest class ValueControllerTest { @Autowired private ValueController controller; @Test void testValueRefresh() throws InterruptedException { String firstValue = controller.getDynamicValue(); // 等待刷新周期(30秒) Thread.sleep(31_000); String secondValue = controller.getDynamicValue(); assertNotEquals(firstValue, secondValue); // 验证值已刷新 // 验证计数器 assertTrue(secondValue.contains("_v1")); // 首刷新后应为v1 } } ``` ##### 方法2:集成测试(MockMvc模拟请求) ```java @SpringBootTest @AutoConfigureMockMvc class ValueControllerIntegrationTest { @Autowired private MockMvc mockMvc; @Test void testApiEndpoint() throws Exception { // 第一请求 MvcResult result1 = mockMvc.perform(get("/dynamic-value")) .andExpect(status().isOk()) .andReturn(); String value1 = result1.getResponse().getContentAsString(); // 等待刷新 Thread.sleep(31_000); // 第二请求 MvcResult result2 = mockMvc.perform(get("/dynamic-value")) .andExpect(status().isOk()) .andReturn(); String value2 = result2.getResponse().getContentAsString(); assertNotEquals(value1, value2); } } ``` #### 3. **关键配置调整** 在启动类启用定时任务: ```java @SpringBootApplication @EnableScheduling // 启用定时任务 public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` #### 4. **执行流程说明** 1. **启动初始化** - 服务启动时立即调用 `refreshValue()` 生成初始值[^1] 2. **定时刷新** - 每30秒通过 `@Scheduled` 触发值刷新 - 更新内存中的 `cachedValue` 和刷新计数器 3. **前端访问** - 前端调用 `/dynamic-value` 获取最新值 - 响应时间:**0.1~1ms**(内存操作无I/O延迟) #### 5. **测试要点验证** | 测试项目 | 验证方法 | 预期结果 | |-------------------|----------------------------------|-----------------------| | 刷新频率 | 记录时间戳差值 | 30±1秒刷新一 | | 值唯一性 | 检查生成值中的时间戳 | 每生成的值不同 | | 线程安全性 | 使用JMeter模拟100并发请求 | 无脏读/数据错乱 | | 服务重启恢复 | 重启后检查初始值 | 自动生成有效值 | | 内存泄漏 | 使用VisualVM监控内存使用 | 无持续增长的内存占用 | --- ### 相关问题 1. 如何在分布式环境中保证所有节点的值同步刷新? 2. 刷新操作耗时较长(超过30秒)时会出现什么问题?如何解决[^2]? 3. 如何通过Spring Actuator监控定时任务执行状态? 4. 值生成失败时如何实现自动重试机制? 5. 前端如何处理刷新期间的值不一致问题[^4]? [^1]: 初始化逻辑确保服务启动时即有有效值可用 [^2]: 超过周期会导致任务堆积,需使用 `@Async` + 线程池隔离 [^4]: 建议前端实现本地缓存并设置29秒过期时间
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老马识途2.0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值