编辑:虽然我同意这个问题的关键在于thread.sleep()的准确性,但我一直认为thread.sleep()倾向于睡得比要求的时间长。为什么线程会在睡眠时间到期之前恢复?我可以理解操作系统调度程序没有及时返回线程来唤醒它,但是为什么它会提前到达呢?如果操作系统可以随意地提前唤醒线程,那么休眠线程又有什么意义呢?
我正在尝试写一个类来在我的项目中进行模块化计时。其思想是使一个类能够测量我感兴趣的任何特定代码段的执行时间。我想做这个测量,而不必写具体的时间代码到位,并为自己提供一个干净的模块化接口。
这个概念是建立在一个教练有多个秒表为他的每个跑步者。我可以用不同的stopwatch id调用一个类来创建测量各自相对执行时间的线程。此外,还有一个圈功能,以衡量子间隔的手表的时钟。实现的中心是stopwatch(coach)类和watch(runner)类都使用hashmap。
以下是我的实现:
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
public class Stopwatch {
private static Map<String, Watch> watchMap = new HashMap<>();
public static boolean start( String watchID ) {
if( !watchMap.containsKey( watchID ) ) {
watchMap.put(watchID, new Watch() );
return true;
} else {
return false;
}
}
public static void stop( String watchID ) {
if( watchMap.containsKey(watchID) ) {
watchMap.get(watchID).stop();
}
}
public static void startLap( String watchID, String lapID ) {
if( watchMap.containsKey(watchID) ) {
watchMap.get(watchID).startLap(lapID);
}
}
public static void endLap( String watchID, String lapID ) {
if( watchMap.containsKey(watchID) ) {
watchMap.get(watchID).stopLap(lapID);
}
}
public static void stopAndSystemPrint( String watchID ) {
if( watchMap.containsKey(watchID)) {
Watch watch = watchMap.get(watchID);
if( watch.isRunning() ) {
watch.stop();
}
Map<String, Long> lapMap = watch.getLapMap();
System.out.println("/******************" + watchID
+ "*******************\\" );
System.out.println("Watch started at: " + watch.getStartTime()
+ " nanosec" );
for( Entry<String, Long> lap : lapMap.entrySet() ) {
System.out.println("\t" + lap.getKey() + ": "
+ ((double)lap.getValue() / 1000000.0)
+ " msec" );
}
System.out.println("Watch ended at: " + watch.getEndTime()
+ " nanosec" );
System.out.println("Watch total duration: "
+ (double)(watch.getDuration() / 1000000.0 )
+ " msec" );
System.out.println("\\******************" + watchID
+ "*******************/\n\n");
}
}
private static class Watch implements Runnable {
private Thread timingThread;
private long startTime;
private long currentTime;
private long endTime;
private volatile boolean running;
private Map<String, Long> lapMap;
public Watch() {
startTime = System.nanoTime();
lapMap = new HashMap<>();
running = true;
timingThread = new Thread( this );
timingThread.start();
}
@Override
public void run() {
while( isRunning() ) {
currentTime = System.nanoTime();
// 0.5 Microsecond resolution
try {
Thread.sleep(0, 500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void stop() {
running = false;
endTime = System.nanoTime();
}
public void startLap( String lapID ) {
lapMap.put( lapID, currentTime );
}
public void stopLap( String lapID ) {
if( lapMap.containsKey( lapID ) ) {
lapMap.put(lapID, currentTime - lapMap.get(lapID) );
}
}
public Map<String, Long> getLapMap() {
return this.lapMap;
}
public boolean isRunning() {
return this.running;
}
public long getStartTime() {
return this.startTime;
}
public long getEndTime() {
return this.endTime;
}
public long getDuration() {
if( isRunning() ) {
return currentTime - startTime;
} else {
return endTime - startTime;
}
}
}
}
下面是我用来测试这个实现的代码:
public class StopwatchTest {
public static void main(String[] args) throws InterruptedException {
String watch1 = "watch1";
Stopwatch.start( watch1 );
String watch2 = "watch2";
Stopwatch.start(watch2);
String watch3 = "watch3";
Stopwatch.start(watch3);
String lap1 = "lap1";
Stopwatch.startLap( watch1, lap1 );
Stopwatch.startLap( watch2, lap1 );
Thread.sleep(13);
Stopwatch.endLap( watch1, lap1 );
String lap2 = "lap2";
Stopwatch.startLap( watch1, lap2 );
Thread.sleep( 500 );
Stopwatch.endLap( watch1, lap2 );
Stopwatch.endLap( watch2, lap1 );
Stopwatch.stop(watch3);
String lap3 = "lap3";
Stopwatch.startLap(watch1, lap3);
Thread.sleep( 5000 );
Stopwatch.endLap(watch1, lap3);
Stopwatch.stop(watch1);
Stopwatch.stop(watch2);
Stopwatch.stop(watch3);
Stopwatch.stopAndSystemPrint(watch1);
Stopwatch.stopAndSystemPrint(watch2);
Stopwatch.stopAndSystemPrint(watch3);
}
}
最后,此测试可以产生的输出:
/******************watch1*******************\
Watch started at: 45843652013177 nanosec
lap1: 12.461469 msec
lap2: 498.615724 msec
lap3: 4999.242803 msec
Watch ended at: 45849165709934 nanosec
Watch total duration: 5513.696757 msec
\******************watch1*******************/
/******************watch2*******************\
Watch started at: 45843652251560 nanosec
lap1: 4.5844165436787E7 msec
Watch ended at: 45849165711920 nanosec
Watch total duration: 5513.46036 msec
\******************watch2*******************/
/******************watch3*******************\
Watch started at: 45843652306520 nanosec
Watch ended at: 45849165713576 nanosec
Watch total duration: 5513.407056 msec
\******************watch3*******************/
这段代码有几个有趣的结果(至少对我来说是这样)。
其一,手表以1毫秒左右的时间早晚结束。我本以为,虽然有点不准确的纳秒时钟,我可以得到更好的精度比1毫秒。也许我忘记了一些重要的数字和准确性。
另一个,在这个测试结果中, watch2
以此结果结束其搭接:
Watch started at: 45843652251560 nanosec
lap1: 4.5844165436787E7 msec
Watch ended at: 45849165711920 nanosec
我检查了我在我的表中操纵值的方式 stopAndSystemPrint
方法,但这似乎对错误没有任何影响。我只能得出这样的结论:我在那里做的数学是可靠的,而且之前的一些东西有时会被打破。有时有点担心我,因为-我想-它告诉我,我可能是做了一些错误的我的线程中 Watch
班级。这似乎是一圈的持续时间被抛出,并导致在我的开始时间和结束时间之间的一些值。
我不确定这些问题是排他性的,但如果我必须选择一个来解决,那就是抖动。
有人知道为什么会有1毫秒左右的抖动吗?
奖励:为什么手表会时不时地弄乱圈时?
1条答案
按热度按时间um6iljoc1#
手表有时会弄乱,因为你是在一个线程中执行计算,读取
currentTime
这与写的线程不同currentTime
. 因此,有时读取的值未初始化—即零。在你提到的具体案件中watch2
,记录了零圈开始时间,因为currentTime
值对于记录圈开始时间的线程不可用。要解决此问题,请声明
currentTime
成为volatile
. 您可能还需要延迟或让步,以允许watch
在开始任何一圈之前做一次更新。至于抖动
currentTime
is not volatile可能是部分或全部问题,因为启动和停止的调用线程可能正在处理过时的数据。另外,thread.sleep()的准确度仅限于系统时钟的准确度,在大多数系统中,这不是纳秒级的准确度。有关后者的更多信息,请参阅评论中可能重复的basilevs。