Java 实现模拟线程#sleep() 背景
我正在设计我的软件,以便能够轻松地执行单元测试。我有一个Java 实现模拟线程#sleep() 背景,java,multithreading,simulation,Java,Multithreading,Simulation,我正在设计我的软件,以便能够轻松地执行单元测试。我有一个IClock接口,其中包括 IClock#等待(时间单位,长持续时间)。此方法将暂停当前线程一段时间(即1秒) IClock接口有两种实现方式: 模拟时钟:具有手动增加时钟中存储的时间的方法 RealClock:通过引用System.currentTimeMillis() 这是IClock#wait(…)的默认方法: 问题 目前我希望模拟单元测试的工作方式是 开始线程 等待所有线程完成或处于阻塞状态(我假设如果它们被阻塞,它们调用了IC
IClock
接口,其中包括
IClock#等待(时间单位,长持续时间)
。此方法将暂停当前线程一段时间(即1秒)
IClock
接口有两种实现方式:
:具有手动增加时钟中存储的时间的方法模拟时钟
:通过引用RealClock
System.currentTimeMillis()
IClock#wait(…)
的默认方法:
问题
目前我希望模拟单元测试的工作方式是
IClock#Wait(…)
)IClock#wait()
Thread#getState()
实现,但我更愿意采用一种更优雅的解决方案,并与ForkJoinPool
一起使用
完整代码
模拟时钟
在调用
simulatedClock.incTimes()
之前,各种线程似乎没有及时运行
通常在多线程测试中,在开始时会有某种“会合”——允许所有线程在安全启动并运行后签入。如果您知道前面将有多少线程,那么使用CountDownLatch
就很容易了
例如,在Simulation.run()
中:
它保留了对倒计时闩锁的引用,以备以后使用
当每个线程到达SimulatedClock.scheduleAt()
时,它可以将闩锁向下计数一次:
@Override
public void scheduleAt(long millis, Runnable runnable)
{
if(millis < getTime())
{
throw new IllegalArgumentException("You are scheduling a task for before the current time!");
}
jobs.add(new Job(millis, runnable));
countDownLatch.countDown();
}
我知道这并不能完全回答您的问题,但您不应该手动创建线程
并使用现有的并发框架
您可以这样做:
public static void main(String[] args) {
AtomicBoolean bool = new AtomicBoolean(false);
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
ScheduledFuture<?> future = executorService.schedule(() -> bool.set(true), 5, TimeUnit.SECONDS);
try {
boolean b = future.get(100, TimeUnit.SECONDS);
} catch (Exception e) {
fail();
}
assertTrue(b);
}
首先,应该将IClock.wait重命名为IClock.sleep以避免混淆
对于RealClock,此方法可以委托给Thread.sleep()
对于模拟时钟,此方法可沿以下线路实施:
void sleep(TimeUnit timeUnit, long dt) {
final Object mon = new Object();
scheduleIn(timeUnit, dt,
() -> {
synchronized(mon) {
mon.notify();
}
});
synchronized(mon) {
try {
mon.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
您可以修改它以抛出InterruptedException。如果您在Linux上运行,有一种秘密的方法。有一个实用程序(我记不起它的名字了,现在谷歌搜索也找不到),你可以用它来运行你的程序。任何时候,当你的程序调用导致睡眠的东西时,它都会截取它,并用零长度睡眠代替它。我认为它也开始在一天的时间上撒谎来补偿
由于这适用于使用glibc编译的任何东西,包括JRE,因此它也适用于任何Java应用程序
再次为未能提供有用的链接表示歉意-我将继续搜索我的书签/搜索,因为这听起来非常有用,我对自己记不起它的名字感到恼火
编辑
这样做的目的是,您不必在自己的程序中有两个单独的实现。用于睡眠的标准库调用在比程序更低的层被截获
这不是我想的,但大致相同:。这似乎是在修补nanosleep()
、clock\u gettime()
和select()
以及一些相关函数,因此它很有可能涵盖了基本功能 但为什么你不能使用线程。睡眠?为什么需要在这里进行模拟?我希望单元测试尽可能快地运行。例如,我可能想模拟一个在现实世界中实际运行100秒但在模拟时可以在毫秒内运行的操作。为什么要手动创建线程并自己管理调度,而不是使用现有的ScheduledExecutor
基础设施?需要明确的是,您希望确保测试中的Runnable
s在n
毫秒内完成,对吗?您在IClock#wait(…)中使用ReentrantLock()完全没有意义,因为每个调用线程都会得到一个单独的锁实例。”“我知道这并不能完全回答您的问题,但是您不应该手动创建线程并使用现有的并发框架。”:这是您的观点fact@gpasch这是Brian Goetz的一句话,所以这并不仅仅是我的观点
@Test
public void testSimpleAction()
{
AtomicBoolean atomicBoolean = new AtomicBoolean(false);
Simulation simulation = new Simulation();
simulation.add(new DealyedAction((TimeUnit.SECONDS, 5) -> atomicBoolean.set(true)));
simulation.run(TimeUnit.SECONDS, 100);
Assert.assertTrue(atomicBoolean.get());
}
simulatedClock.init(new CountDownLatch(actions.size()));
@Override
public void scheduleAt(long millis, Runnable runnable)
{
if(millis < getTime())
{
throw new IllegalArgumentException("You are scheduling a task for before the current time!");
}
jobs.add(new Job(millis, runnable));
countDownLatch.countDown();
}
public void incTimes(long times, long dt)
{
countDownLatch.await();
long init = getTime();
...
public static void main(String[] args) {
AtomicBoolean bool = new AtomicBoolean(false);
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
ScheduledFuture<?> future = executorService.schedule(() -> bool.set(true), 5, TimeUnit.SECONDS);
try {
boolean b = future.get(100, TimeUnit.SECONDS);
} catch (Exception e) {
fail();
}
assertTrue(b);
}
interface Clock {
Future<?> scheduleAt(long millis, Runnable r);
}
class SchedulerService implements Clock {
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
public Future<?> scheduleAt(long millis, Runnable r) {
Instant scheduleTime = Instant.ofEpochMilli(millis);
Instant now = Instant.now();
if (scheduleTime.isBefore(now)) {
throw new IllegalArgumentException("You are scheduling a task for before the current time!");
}
long delay = scheduleTime.minus(now).toEpochMilli();
return executorService.schedule(r, delay, TimeUnit.MILLISECONDS);
}
}
void sleep(TimeUnit timeUnit, long dt) {
final Object mon = new Object();
scheduleIn(timeUnit, dt,
() -> {
synchronized(mon) {
mon.notify();
}
});
synchronized(mon) {
try {
mon.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}