cn.keayuan.util.Timer Maven / Gradle / Ivy
The newest version!
package cn.keayuan.util;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by keayuan on 2024.10.14.
*
* @author keayuan
*/
public final class Timer {
private final Queue queue = new Queue();
private final Thread thread = new Thread(this::loop, "SSK-Timer");
private static final Timer instance = new Timer();
private Timer() {
thread.setDaemon(true);
thread.setPriority(Thread.MAX_PRIORITY);
thread.start();
}
private void loop() {
try {
while (true) {
Task task = queue.get();
task.run();
}
} catch (InterruptedException ignored) {
}
}
public static long currentTime() {
return System.nanoTime() / 1000000;
}
public static Task add(Runnable runnable) {
return add(null, runnable);
}
public static Task add(long delay, Runnable runnable) {
return add(delay, null, runnable);
}
public static Task add(Executor executor, Runnable runnable) {
return add(0, executor, runnable);
}
public static Task add(long delay, Executor executor, Runnable runnable) {
Task task = new Task(currentTime() + delay, runnable, executor);
instance.queue.addTask(task);
return task;
}
public static TaskBuilder builder(Runnable runnable) {
return new TaskBuilder(runnable);
}
public static class Task {
private long when;
private volatile boolean isCancel;
private final Runnable runnable;
private final Executor executor;
private final long period;
private final AtomicInteger count;
private Task next;
private Task(TaskBuilder builder) {
runnable = builder.runnable;
this.when = currentTime() + builder.delay;
period = builder.period;
count = new AtomicInteger(builder.count);
executor = builder.executor;
}
private Task(long when, Runnable runnable, Executor executor) {
this.when = when;
this.runnable = runnable;
this.executor = executor;
period = 0;
count = new AtomicInteger(1);
}
boolean isExpired() {
return currentTime() >= when;
}
long remaining() {
return when - currentTime();
}
public void cancel() {
isCancel = true;
}
void run() {
if (isCancel) return;
if (executor == null) realRun();
else executor.execute(this::realRun);
}
void realRun() {
if (isCancel) return;
try {
runnable.run();
} catch (Exception ignored) {
} finally {
if (count.decrementAndGet() != 0) {
when += period;
instance.queue.addTask(this);
}
}
}
}
public static class TaskBuilder {
private final Runnable runnable;
private Executor executor;
private long delay;
private long period;
private int count = 1;
public TaskBuilder(Runnable runnable) {
if (runnable == null) throw new IllegalArgumentException("runnable can not be null");
this.runnable = runnable;
}
public TaskBuilder executor(Executor executor) {
this.executor = executor;
return this;
}
public TaskBuilder delay(long delay) {
this.delay = delay;
return this;
}
public TaskBuilder period(long period, int count) {
if (period < 20) throw new IllegalArgumentException("period cannot be less than 20");
if (count == 0) throw new IllegalArgumentException("count cannot be equals 0");
this.period = period;
this.count = count;
return this;
}
public TaskBuilder period(long period) {
return period(period, -1);
}
public Task start() {
Task task = new Task(this);
instance.queue.addTask(task);
return task;
}
}
private static class Queue {
Task head;
private final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
void addTask(Task task) {
if (task.runnable == null || task.isCancel || task.count.get() == 0) return;
lock.lock();
try {
if (head == null) {
head = task;
notEmpty.signal();
return;
}
if (task.when < head.when) {
task.next = head;
head = task;
notEmpty.signal();
return;
}
Task pre = head;
while (pre.next != null && pre.next.when < task.when) pre = pre.next;
Task temp = pre.next;
pre.next = task;
task.next = temp;
notEmpty.signal();
} finally {
lock.unlock();
}
}
Task get() throws InterruptedException {
lock.lock();
try {
if (head == null) notEmpty.await();
if (!head.isExpired()) {
notEmpty.await(head.remaining(), TimeUnit.MILLISECONDS);
return get();
}
Task task = head;
head = head.next;
return task;
} finally {
lock.unlock();
}
}
}
}