First idea: do the Scanner in a separate thread
As you've noticed, Scanner.nextLine blocks until the user has input something and pressed Enter. We can ignore this problem if our clock does not run in the same thread as the Scanner loop. Let's use a daemon thread for reading user input.
I've picked the word "off" as stopping input from the user.
import java.util.Scanner;
public class Clock1 {
private static volatile boolean on = true;
public static void main(String[] args) throws InterruptedException {
int s = 0, m = 0, h = 0;
Thread t = new Thread(() -> {
try (Scanner scan = new Scanner(System.in)) {
while (on) {
String input = scan.nextLine();
if (input == null)
continue;
if ("off".equalsIgnoreCase(input.trim())) {
on = false;
}
}
}
});
t.setName("console");
t.setDaemon(true);
t.start();
clock: for (int i = 0; i < 2; i++) { //Why this loop ?
long prevTime = System.nanoTime();
long diff;
while (on) {
if (++s % 60 == 0) {
s = 0;
if (++m % 60 == 0) {
m = 0;
++h;
}
}
do {
diff = System.nanoTime() - prevTime;
Thread.sleep(Math.min(1000 - diff / 1_000_000L, 100));
} while (on && diff < 1_000_000_000L);
if (!on)
break clock;
prevTime = System.nanoTime();
System.out.println(String.format("%02d:%02d:%02d", h, m, s));
}
}
System.out.println("Clock stopped");
}
}
You need to be aware that timing a clock with Thread.sleep is inaccurate, though. But it may be enough for your problem...
I tried to compensate for this using several checks for System.nanoTime per second, but accurate timing would require a more complex algorithm. It also allows for about immediate reaction to user input.
Second idea: use a BufferedReader rather than a Scanner
Using a BufferedReader allows us to avoid blocking execution while we check if the buffer is ready, and then only read user input when they've pressed Enter, thus bypassing the problem with Scanner.nextLine.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
public class Clock2 {
private static volatile boolean on = true;
public static void main(String[] args) throws InterruptedException, IOException {
int s = 0, m = 0, h = 0;
try (BufferedReader rdr = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8))) {
clock: for (int i = 0; i < 2; i++) { // Why this loop ?
long prevTime = System.nanoTime();
long diff;
while (on) {
if (++s % 60 == 0) {
s = 0;
if (++m % 60 == 0) {
m = 0;
++h;
}
}
do {
diff = System.nanoTime() - prevTime;
Thread.sleep(Math.min(1000 - diff / 1_000_000L, 100));
String line;
while (on && rdr.ready() && (line = rdr.readLine()) != null) {
if (line.trim().equalsIgnoreCase("off"))
on = false;
}
} while (on && diff < 1_000_000_000L);
if (!on)
break clock;
prevTime = System.nanoTime();
System.out.println(String.format("%02d:%02d:%02d", h, m, s));
}
}
}
System.out.println("Clock stopped");
}
}
Third idea: use a ScheduledExecutor
This time, the clock is timed by a ScheduledExecutor that executes the increment loop once per second in a separate thread as the main program.
Accumulated values had to be stored in a class, so I also implemented AutoCloseable with class Clock3 to ease shutting down the executor when the clock stops.
User input is read in a loop in the main thread using a Scanner until we hit the word "off".
import java.util.Scanner;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Clock3 implements AutoCloseable {
private static volatile boolean on = true;
private ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
private int s = 0, m = 0, h = 0;
public static void main(String[] args) throws Exception {
try (Clock3 clock = new Clock3()) {
try (Scanner scan = new Scanner(System.in)) {
while (on) {
String input = scan.nextLine();
if (input == null)
continue;
if ("off".equalsIgnoreCase(input.trim())) {
on = false;
}
}
}
}
System.out.println("Clock stopped");
}
public Clock3() {
exec.scheduleAtFixedRate(() -> {
if (++s % 60 == 0) {
s = 0;
if (++m % 60 == 0) {
m = 0;
++h;
}
}
if (on)
System.out.println(String.format("%02d:%02d:%02d", h, m, s));
}, 0, 1, TimeUnit.SECONDS);
}
@Override
public void close() throws Exception {
exec.shutdownNow();
}
}
I don't know how accurate this scheduling is guaranteed to be. From what I can read from documentation for ScheduledExecutorService.scheduleAtFixedRate:
If any execution of this task takes longer than its period, then subsequent executions may start late.
Which will never be the case with this small increment algorithm.
There are probably better ways of scheduling that however, but that was not the point of your question...