Java, Как сделать точную задержку для секундомера?
Есть следующий метод запуска секундомера. Ещё не дописан до конца, но работает.
private void startStopwatch() {
stopwatchRunning = true;
Thread thread = new Thread() {
public void run() {
while (stopwatchRunning) {
msCounter++;
msStr = String.valueOf(msCounter);
secStr = String.valueOf(sec);
minStr = String.valueOf(min);
if (msCounter < 10) {
msStr = "0" + msCounter;
}
if (msCounter == 99) {
//Прошла ещё 1 секунда
sec++;
msCounter = 0;
}
//timeViewStopwatch.setText(msStr);
timeViewStopwatch.setText(minStr + ":" + secStr + ":" + msStr);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
thread.start();
}
Проблема в том, что когда я проверил секундомер по часам, он отстал на целых 13 секунд...
P.S. Делать задежку через TimeUnit я тоже пробовал, но он как отставал, так и отстаёт.
Ответы (3 шт):
Проблема вашего решения в том, что оно не учитывает время выполнения кода, именно на это время показания расходятся с реальным.
private void startStopwatch() {
stopwatchRunning = true;
Thread thread = new Thread() {
public void run() {
long interval = 10;
long nextTime = System.currentTimeMillis() + interval;
while (stopwatchRunning) {
msCounter++;
msStr = String.valueOf(msCounter);
secStr = String.valueOf(sec);
minStr = String.valueOf(min);
if (msCounter < 10) {
msStr = "0" + msCounter;
}
if (msCounter == 99) {
//Прошла ещё 1 секунда
sec++;
msCounter = 0;
}
//timeViewStopwatch.setText(msStr);
timeViewStopwatch.setText(minStr + ":" + secStr + ":" + msStr);
long sleep = nextTime - System.currentTimeMillis();
if (sleep > 0) {
try {
Thread.sleep(sleep);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
nextTime += interval;
}
}
};
thread.start();
}
Интервала в 10 мсек может не хватить на выполнение кода итерации и тогда время снова будет расходиться с реальным. Нужно либо увеличить интервал, либо оптимизировать код.
Я решил проблему, использовав готовый секундомер из библиотеки Apache Commons Lang. Вот код, он показывает часы:минуты:секунды.сотые доли секунды (насчёт правильности показа часов я не уверен, ещё не успел проверить).
private void startStopwatch() {
StopWatch stopwatch = StopWatch.createStarted();
msCounter = 0;
Thread thread = new Thread() {
public void run() {
while (stopwatchRunning) {
msCounter++;
if (msCounter == 99) {
msCounter = 0;
//sec++;
}
if (msCounter >= 10) {
msStr = String.valueOf(msCounter);
} else {
msStr = "0" + msCounter;
}
if (min == 0) {
sec = stopwatch.getTime(TimeUnit.SECONDS);
} else {
sec = stopwatch.getTime(TimeUnit.SECONDS) - (min * 60);
}
if (hr == 0) {
min = stopwatch.getTime(TimeUnit.MINUTES);
} else {
min = stopwatch.getTime(TimeUnit.MINUTES) - (hr * 60);
}
hr = stopwatch.getTime(TimeUnit.HOURS);
secStr = String.valueOf(sec);
minStr = String.valueOf(min);
hrStr = String.valueOf(hr);
if (min >= 10) {
minStr = String.valueOf(min);
} else {
minStr = "0" + min;
}
if (sec == 60) {
sec = 0;
}
if (sec >= 10) {
secStr = String.valueOf(sec);
} else {
secStr = "0" + sec;
}
if (hr == 60) {
hr = 0;
}
if (hr >= 10) {
hrStr = String.valueOf(hr);
} else {
hrStr = "0" + hr;
}
timeViewStopwatch.setText(hrStr + ":" + minStr + ":" + secStr + "." + msStr);
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
thread.start();
}
UPD: лучший вариант предложен в комментарии Станиславом.
А этот ответ я не знаю как удалить.
Thread.sleep(10) спит примерно 10мс.
Плюс остальной код выполняется какое-то время, а не мгновенно.
Вот в итоге ошибка и накапливается.
Решение: надо проверять, сколько времени прошло, каждый раз, как поток просыпается.
Например, так:
static long ONE_MILLISECOND_NANOS = Duration.ofSeconds(1).toNanos();
long nextTimeNanos = System.nanoTime() + ONE_MILLISECOND_NANOS;
TimeUnit.NANOSECONDS.sleep(ONE_MILLISECOND_NANOS);
while(stopwatchRunning && !Thread.currentThread().isInterrupted()) {
long currentTimeNanos = System.nanoTime();
long timeLeftNanos = nextTimeNanos - currentTimeNanos;
if (timeLeftNanos > 0) {
TimeUnit.NANOSECONDS.sleep(timeLeftNanos);
} else {
long milliSecondsPassed = (-timeLeftNanos) / ONE_MILLISECOND_NANOS;
// обновить число прошедших миллисекунд в UI
nextTimeNanos = nextTimeNanos + (milliSecondsPassed + 1) * ONE_MILLISECOND_NANOS;
TimeUnit.NANOSECONDS.sleep(nextTimeNanos - currentTimeNanos);
}
}