background
When it comes to scheduled tasks, the author has contacted crontable and spirng-schedule. Quartz has many implementation methods, but the bottom layer depends on the interruption of the operating system. Today, we are going to introduce a practical scheduled task time wheel, which can add points to the interview. The time-wheel algorithm was published in a paper “Hashed and Hierarchical Timing Wheels”. Of course, it has also been introduced in Java frameworks, such as Netty, Dubbo, etc.
design
Let’s talk about his design first, which is very similar to the clock in real life. The minute pointer moves from 0 to 12 at a constant speed, which is an hour. Then let’s think about hashmap. Let’s implement a simpler approach.
The design
To illustrate the structure of the figure above, there are several concepts,Task manager
: MyHashedWheelTimer is a container for task submission and execution,Task queue
: HashedWheelBucket is a task assembly container in the same Hash slot. HashedWheelTimeout packs each task extension Pre, next, task execution time, etc., which contains the task of the smallest atomTimerTask
, the length of time for the pointer to jump once in a cycle istickDuration
.
The code
Let’s build several class models according to the design on the diagram.
public class MyHashedWheelTimer {
// Invoke the execution object of the scheduled task
private final Worker worker = new Worker();
/** * The millisecond when the scheduled task manager starts */
private volatile long startTime;
// The number of points that the pointer passes in a circle
private final int mask;
private final HashedWheelBucket[] wheel;
/** * The smallest unit of clock rotation */
private final long tickDuration;
// Initialize the task manager according to the time when the pointer jumps once
public MyHashedWheelTimer(long tickDuration) {}
// Add the timeout task 'task' to the task manager for subsequent triggering
public HashedWheelTimeout newTimeout(Runnable task, long delay, TimeUnit unit){}}private static final class HashedWheelBucket {
private HashedWheelTimeout head;/ / queue head
private HashedWheelTimeout tail;/ / queue tail
// Add the timeout task to the hash slot queue
void addTimeout(HashedWheelTimeout timeout) {}
// Execute the timeout task
void doTimeouts(long deadline, long tick) {}
// Remove the task after executing the task
public HashedWheelTimeout remove(HashedWheelTimeout timeout) {}}private final class Worker implements Runnable {
private long tick;// The number of pointer jumps continues to stack as the task manager starts
@Override
public void run(a) {
do {
final long deadline = waitForNextTick();// The time after each hop
/ /... Find the corresponding task and execute it
} while (true);
}
private long waitForNextTick(a) {
// The pointer jumps once}}// Wrap the task and its execution time
private static final class HashedWheelTimeout {
long remainingRounds;// The task will be executed after how many times the clock has been turned from start to end
HashedWheelTimeout next;
HashedWheelTimeout prev;
HashedWheelBucket bucket;
long deadline;
Runnable task;
HashedWheelTimeout(Runnable task, long deadline) {
this.task = task;
this.deadline = deadline; }}Copy the code
The above four classes provide the most basic functionality, but we will supplement the implementation below
public MyHashedWheelTimer(long tickDuration) {
wheel = createWheel(64);// 64 cells per cycle by default
mask = wheel.length - 1;// Find the hash ring where the task is located, for example, a & mask = idx
this.tickDuration = TimeUnit.MILLISECONDS.toMillis(tickDuration);// The jump time
startTime = System.currentTimeMillis();// Task manager start event
new Thread(worker).start();// Start the task calling thread
}
// Create a HashedWheelBucket object on each hash slot
private static HashedWheelBucket[] createWheel(int ticksPerWheel) {
HashedWheelBucket[] wheel = new HashedWheelBucket[ticksPerWheel];
for (int i = 0; i < wheel.length; i++) {
wheel[i] = new HashedWheelBucket();
}
return wheel;
}
public HashedWheelTimeout newTimeout(Runnable task, long delay, TimeUnit unit) {
// Which millisecond in the future will the task be executed
long deadline = System.currentTimeMillis() + unit.toMillis(delay) - startTime;
HashedWheelTimeout timeout = new HashedWheelTimeout(task, deadline);
// Task execution time from the number of cycles required to start the task manager
timeout.remainingRounds = (deadline / tickDuration);
// What position index will the task fall on in the hash slot
int stopIndex = (int) ((deadline / tickDuration) & mask);
HashedWheelBucket bucket = wheel[stopIndex];
bucket.addTimeout(timeout);// Add tasks to the list of hash slots
return timeout;
}
private final class Worker implements Runnable {
public void run(a) {
do {
final long deadline = waitForNextTick();
if (deadline > 0) {
int idx = (int) (tick & mask); // & 64
HashedWheelBucket bucket =
wheel[idx];
bucket.doTimeouts(deadline, tick);// Executes scheduled tasks that meet the conditions in the hash slottick++; }}while (true); }}void addTimeout(HashedWheelTimeout timeout) {
// Add nodes to bidirectional lists
}
public HashedWheelTimeout remove(HashedWheelTimeout timeout) {
// Delete a node from the list
}
// Iterate through all tasks in the list
void doTimeouts(long deadline, long tick) {
HashedWheelTimeout timeout = head;
while(timeout ! =null) {
HashedWheelTimeout next = timeout.next;
// If remainingRounds(number of pointer jumps required)-tick(number of jumps already) <=0 is met, task execution is triggered
if (timeout.remainingRounds - tick <= 0) {
next = remove(timeout);
if (timeout.deadline <= deadline) {
timeout.task.run();// Call the task's run() method, similar to a thread pool
} else {
throw newIllegalStateException(); } } timeout = next; }}Copy the code
Analysis of the
The code is just the implementation of basic functions, specific we can see and Dubbo or Netty implementation difference, such as task management does not start and stop, task submission can not be cancelled, thread is not safe, input is not rich enough, the number of tasks is not controllable and so on.
thinking
- The task executed by a time wheel pair will be processed only once. How to implement a periodic task?
- What if the task has to wait a long time to execute?
- If you have a lot of tasks that are very intensive how do you get the task closest to the theoretical execution time?
conclusion
The code is not complete, if you need complete code can leave a message.