Implement Simulated Thread#sleep()
Implement Simulated Thread#sleep()
Background
I am designing my software so that I can easily perform unit tests. I have an IClock
interface which, among other methods, has IClock#wait(TimeUnit timeUnit, long duration)
. This method will halt the current thread for timeUnit duration (i.e. 1 second).
IClock
IClock#wait(TimeUnit timeUnit, long duration)
There are two implementations of the IClock
interface:
IClock
SimulatedClock
RealClock
System.currentTimeMillis()
This is the default method for IClock#wait(...)
:
IClock#wait(...)
/**
* Locks current thread for specified time
*
* @param timeUnit
* @param dt
*/
default void wait(TimeUnit timeUnit, long dt)
Lock lock = new ReentrantLock();
scheduleIn(timeUnit, dt, lock::unlock);
lock.lock();
Problem
The current way I want simulated unit tests to work is
IClock#wait(...)
SimulatedClock
However, what is really happening is:
IClock#wait()
So, what I need to do is to be able to determine when all threads are done or blocked. Although this can be done with Thread#getState()
, I would rather employ a solution which is more elegant and works with ForkJoinPool
.
Thread#getState()
ForkJoinPool
Full Code
GitHub
package com.team2502.ezauton.utils;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class SimulatedClock implements IClock
private long time = 0;
private List<Job> jobs = new ArrayList<>();
public SimulatedClock()
public void init()
init(System.currentTimeMillis());
public void init(long time)
setTime(time);
/**
* Add time in milliseconds
*
* @param dt millisecond increase
* @return The new time
*/
public long addTime(long dt)
setTime(getTime() + dt);
return getTime();
/**
* Adds time with units
*
* @param timeUnit
* @param value
*/
public void addTime(TimeUnit timeUnit, long value)
addTime(timeUnit.toMillis(value));
/**
* Add one millisecond and returns new value
*
* @return The new time
*/
public long incAndGet()
return addTime(1);
/**
* Increment a certain amount of times
*
* @param times
*/
public void incTimes(long times, long dt)
long init = getTime();
long totalDt = times * dt;
for(int i = 0; i < times; i++)
if(!jobs.isEmpty())
addTime(dt);
else
break;
setTime(init + totalDt);
/**
* Increment a certain amount of times
*
* @param times
* @return
*/
public void incTimes(long times)
incTimes(times, 1);
@Override
public long getTime()
return time;
public void setTime(long time)
jobs.removeIf(job ->
if(job.getMillis() < time)
job.getRunnable().run();
return true;
return false;
);
this.time = time;
@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));
private static class Job
private final long millis;
private final Runnable runnable;
public Job(long millis, Runnable runnable)
this.millis = millis;
this.runnable = runnable;
public long getMillis()
return millis;
public Runnable getRunnable()
return runnable;
package com.team2502.ezauton.command;
import com.team2502.ezauton.utils.SimulatedClock;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
public class Simulation
private final SimulatedClock simulatedClock;
private List<IAction> actions = new ArrayList<>();
public Simulation()
simulatedClock = new SimulatedClock();
public SimulatedClock getSimulatedClock()
return simulatedClock;
public Simulation add(IAction action)
actions.add(action);
return this;
/**
* @param timeoutMillis Max millis
*/
public void run(long timeoutMillis)
simulatedClock.init();
actions.forEach(action -> new ThreadBuilder(action, simulatedClock).buildAndRun());
simulatedClock.incTimes(timeoutMillis);
// Need to wait until all threads are finished
if(!ForkJoinPool.commonPool().awaitQuiescence(1, TimeUnit.SECONDS))
throw new RuntimeException("Simulator did not finish in a second.");
public void run(TimeUnit timeUnit, long value)
run(timeUnit.toMillis(value));
@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());
The question is widely applicable to a large audience. A detailed canonical answer is required to address all the concerns.
Provide a working, simulateable Thread.sleep() implementation.
I want the unit tests to be run as fast as possible. For example, I might want to simulate an action which would actually run for 100 seconds in the real world but could be run in a matter of milliseconds when simulated.
– Andrew Gazelka
2 days ago
Why do you create Threads manually and manage the scheduling yourself, rather than using the existing
ScheduledExecutor
infrastructure? And to be clear, you want to make sure that the Runnable
s under test are finished by n
milliseconds, right?– daniu
yesterday
ScheduledExecutor
Runnable
n
Your use of ReentrantLock() in IClock#wait(...) makes absolutely no sense because each calling thread would get a separate instance of the lock.
– tbsalling
yesterday
2 Answers
2
That looks like the various threads are not getting going in time, before simulatedClock.incTimes()
is called.
simulatedClock.incTimes()
Often with a multi-threaded test, there is some kind of 'rendezvous' at the start - to allow all of the threads to check-in once they are safely up and running. If you know how many threads there are going to be up front, a CountDownLatch
makes this easy.
CountDownLatch
E.g. in Simulation.run()
:
Simulation.run()
simulatedClock.init(new CountDownLatch(actions.size()));
Which keeps a reference to the CountDownLatch
for later.
CountDownLatch
When each thread arrives in SimulatedClock.scheduleAt()
, it can count the latch down by one:
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();
Then incTimes()
can wait for all the threads to show up:
incTimes()
public void incTimes(long times, long dt)
{
countDownLatch.await();
long init = getTime();
...
I know this doesn't exactly answer your question, but you shouldn't create Thread
s manually and use the existing concurrency framework.
Thread
You can just do this:
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);
You can also integrate it into your framework if your scheduleAt
returns the Future
.
scheduleAt
Future
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);
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
But why you can't use Thread.sleep? Why you need to simulate it here?
– GotoFinal
Aug 20 at 20:44