根据评论,主要目标不是在这个精确的时间间隔内同时执行多个任务.相反,目标是尽可能精确地以此间隔执行单个任务.
不幸的是,在这个意义上,ScheduledExecutorService和任何涉及Thread#sleep或LockSupport#parkNanos的手动构造都不是很精确.正如其他答案中所指出的:可能总会有一些影响因素超出您的控制范围 – 即JVM实施,垃圾收集,JIT运行等细节.
然而,在这里实现高精度的相对简单的方法是忙着等待. (这已在现已删除的答案中提及).但当然,这有几点需要注意.最重要的是,它将刻录一个CPU的处理资源. (在单CPU系统上,这可能特别糟糕).
但是为了表明它可能比其他等待方法更精确,这里是ScheduledExecutorService方法和忙碌等待的简单比较:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class PreciseSchedulingTest
{
public static void main(String[] args)
{
long periodMs = 50;
PreciseSchedulingA a = new PreciseSchedulingA();
a.setup(periodMs);
PreciseSchedulingB b = new PreciseSchedulingB();
b.setup(periodMs);
}
}
class CallTracker implements Runnable
{
String name;
long expectedPeriodMs;
long baseTimeNs;
long callTimesNs[];
int numCalls;
int currentCall;
CallTracker(String name, long expectedPeriodMs)
{
this.name = name;
this.expectedPeriodMs = expectedPeriodMs;
this.baseTimeNs = System.nanoTime();
this.numCalls = 50;
this.callTimesNs = new long[numCalls];
}
@Override
public void run()
{
callTimesNs[currentCall] = System.nanoTime();
currentCall++;
if (currentCall == numCalls)
{
currentCall = 0;
double maxErrorMs = 0;
for (int i = 1; i < numCalls; i++)
{
long ns = callTimesNs[i] - callTimesNs[i - 1];
double ms = ns * 1e-6;
double errorMs = ms - expectedPeriodMs;
if (Math.abs(errorMs) > Math.abs(maxErrorMs))
{
maxErrorMs = errorMs;
}
//System.out.println(errorMs);
}
System.out.println(name + ", maxErrorMs : " + maxErrorMs);
}
}
}
class PreciseSchedulingA
{
public void setup(long periodMs)
{
CallTracker callTracker = new CallTracker("A", periodMs);
ScheduledExecutorService se = Executors.newScheduledThreadPool(20);
se.scheduleAtFixedRate(callTracker, periodMs,
periodMs, TimeUnit.MILLISECONDS);
}
}
class PreciseSchedulingB
{
public void setup(long periodMs)
{
CallTracker callTracker = new CallTracker("B", periodMs);
Thread thread = new Thread(new Runnable()
{
@Override
public void run()
{
while (true)
{
long periodNs = periodMs * 1000 * 1000;
long endNs = System.nanoTime() + periodNs;
while (System.nanoTime() < endNs)
{
// Busy waiting...
}
callTracker.run();
}
}
});
thread.setDaemon(true);
thread.start();
}
}
同样,这应该是一粒盐,但MyMachine®上的结果如下:
A, maxErrorMs : 1.7585339999999974
B, maxErrorMs : 0.06753599999999693
A, maxErrorMs : 1.7669149999999973
B, maxErrorMs : 0.007193999999998368
A, maxErrorMs : 1.7775299999999987
B, maxErrorMs : 0.012780999999996823
显示等待时间的错误在几微秒的范围内.
为了在实践中应用这种方法,需要更复杂的基础设施.例如.为了弥补过高的等待时间而需要的簿记. (我认为它们不能太低).而且,所有这些仍然不能保证精确定时执行.但至少可以考虑它.