2

I am new to Java and I am trying to use one Thread to finish a loop in another Thread regardless of the state of the loop.

public static void main(String[] args) {
    // Either start the other thread here
        while(true){
            // Or here, not quite sure
            // Do stuff
        }
    }
}



public class Timer implements Runnable{

    @Override
    public void run() {
        long start = System.currentTimeMillis();
        while(true) {
            long current = System.currentTimeMillis();
            if(current - start == 10000){
                // How do I notify the loop in main to break?
                break;
            }
        }
    }
}

What I am trying to do is end the loop in main after 10 seconds, regardless of the state of its loop because the loop contains a Scanner reading from System.in, and it needs to stop after 10 seconds regardless of what has been read from the keyboard or not. I thought the best solution would be to have a timer Thread running which counts the seconds, and then notify the loop in the other Thread somehow, after 10 seconds, to break, however, I do not know how to implement this...

AMDG
  • 1,118
  • 1
  • 13
  • 29
Andrew
  • 41
  • 1
  • 5
  • Why do you need another thread to count the time? – shmosel Jan 06 '17 at 01:10
  • I thought I need another thread so it won't interfere with the steps executed in the Main loop. – Andrew Jan 06 '17 at 01:13
  • You thought the best solution was to code a thread to do something you didn't actually want it to do and then have some other thread reach in and make it do the right thing? Well, no. The best thing is to code the thread to do what, and only what, you actually want it to do. That way you don't have to force it to do the right thing. – David Schwartz Jan 06 '17 at 01:39
  • Code the thread with the loop properly. This is the wrong approach. – duffymo Jan 06 '17 at 02:12
  • Ok, I managed to solve it like this: I swapped the loop and the counter so that Main contains the timer (the loop with the miliseconds), and the other Thread contains the loop with the Scanner.readLine(). When the Main loop reaches 10 seconds, it breaks, and calls for the other Thread to stop: Thread.interrupt(). Though I had the surprise that Thread.interrupt() did not stop the Scanner.readline();, calling for System.exit(0) did stop it, and so the program has the intended functionality overall. Thank you everyone for your help! – Andrew Jan 06 '17 at 16:06

5 Answers5

3

If your goal is to stop looping after 10 seconds, there's no reason to use another thread. Just check the time locally:

public class Main {
    public static void main(String[] args) {
        // Instructions

        long start = System.currentTimeMillis();
        do {
            //Instructions
            //Instructions
            //...
            //Instructions   
        } while (System.currentTimeMillis() - start < 10000);
    }
}
shmosel
  • 49,289
  • 6
  • 73
  • 138
  • If I do so, will the condition System.currentTimeMillis() - start < 10000 be checked regardless of the loop state? Or when the instructions in the loop are finished, and the loop is ready to repeat? – Andrew Jan 06 '17 at 01:18
  • 1
    @Andrew It will be checked before each repetition. – shmosel Jan 06 '17 at 01:19
  • Well, I need the loop to finish after exactly 10 seconds, and some operations in it have to be interrupted/skipped/ignored. Most of them are easy operations (creating some objects and comparing some numbers), but I also have a Scanner.readLine(); (I read on internet Scanner is blocking the thread (?) or it might be blocking only when reading from Sockets). Anyhow, I want operations in the loop to be skipped/ignored when the condition is met, not before repeating the instructions. – Andrew Jan 06 '17 at 01:26
  • @Andrew Then none of these answers are going to work. You'll have to write custom code to check the time or flag at each step. – shmosel Jan 06 '17 at 01:27
  • Hmm, I guess I will have to check after every single step. However, is Scanner.readLine() going to be skipped? I mean, I don't want to have to write something and press Enter in order to reach the next step which would be the verification and break. I want somehow to ignore the Scanner.readLine() when the condition is met. – Andrew Jan 06 '17 at 01:34
  • @Andrew Interrupting a thread blocked on an input stream is a whole other issue. See [here](http://stackoverflow.com/questions/6008177/java-how-to-abort-a-thread-reading-from-system-in) and [here](http://stackoverflow.com/questions/804951/is-it-possible-to-read-from-a-inputstream-with-a-timeout) for possible solutions. – shmosel Jan 06 '17 at 01:42
2

How about something like this:

public static void main(String[] args) {
    // Instructions
    AtomicBoolean shouldStop = new AtomicBoolean(false);

   Timer timer = new Timer(shouldStop);
   // start the timer thread
        while(true){
            if (shouldStop.get()) {
               break;
            }

            //Instructions
        }
    }
}



public class Timer implements Runnable{
   private final AtomicBoolean shouldStop;

   public Timer(AtomicBoolean shouldStop) {
        this.shouldStop = shouldStop;
   }

    @Override
    public void run() {
        long start = System.currentTimeMillis();
        while(true){
            long current = System.currentTimeMillis();
            if(current - start == 10000) {
                shouldStop.set(true);
                break;
            }
        }
    }
}
Oliver Dain
  • 9,617
  • 3
  • 35
  • 48
  • BTW: The other two answers are correct that you don't need another thread and that a Sleep would be more efficient. My answer assumes you were really asking about how to send a signal from one thread to another. But if what you really want to do is just stop after 10 seconds take the advice from one of the other answers. – Oliver Dain Jan 06 '17 at 01:14
  • Yup, `volatile` would be fine here. – Oliver Dain Jan 06 '17 at 01:15
  • It would be better to use `while (!shouldStop.get()) {`. – Chai T. Rex Jan 06 '17 at 01:30
  • @ChaiT.Rex agreed. Was trying to show a solution with only minimal edits to his initial code. I thought that might be more clear. – Oliver Dain Jan 06 '17 at 18:35
2

The safest approach to your problem is to use a volatile variable:

public class Main {

    private static volatile boolean keepRunning = false;

    public static void main(String[] args) {
        keepRunning = true;

        while(keepRunning) {
            //Instructions
            //...
            //Instructions   
        }
    }
}

public class Timer implements Runnable {

    @Override
    public void run() {
        long start = System.currentTimeMillis();
        while(true){
            long current = System.currentTimeMillis();
            if(current - start == 10000){
                // Notify the loop in Main to break
                keepRunning = false;
                break;
            }
        }
    }

}
AMDG
  • 1,118
  • 1
  • 13
  • 29
Roberto Attias
  • 1,883
  • 1
  • 11
  • 21
  • This is close to what I need, but how can I use the var keepRunning to break the loop in main, regardless of its state? Not when all the instructions in the loop are finished, and ready to repeat, but exactly when keepRunning is false, meaning I want the instructions in the loop to be skipped/ignored right when keepRunning becomes false. – Andrew Jan 06 '17 at 01:30
  • (ppl, if you downvote, explain why). To your question, the answer is: you can't. With multithreading there is no "right when". If some of your instructions are blocking calls, (reading from file/socket, sleep, etc) you can call `Thread.interrupt()`, see https://docs.oracle.com/javase/tutorial/essential/concurrency/interrupt.html. But as far as pure code is concerned, this is still a collaborative mechanism. – Roberto Attias Jan 06 '17 at 01:49
  • 2
    I have no idea why such a simple and performance friendly solution is down-voted. The Java programmers need to keep the ability to write up elegant code with KISS principle, and not to use massive framework provided in JDK every time. – Alex Suo Jan 06 '17 at 07:48
2

Don't reinvent the wheel. Use an ExecutorService and a get() with a timeout:

ExecutorService executor = Executors.newSingleThreadExecutor();

// Here's a lambda, but you could use an instance of a normal Runnable class
Runnable runnable = () -> {
    while(true){
        // Do stuff
    }
};

Future<?> future = executor.submit(runnable);

try {
    future.get(10, TimeUnit.SECONDS);
} catch (TimeoutException e) {
    // the task timed out
    future.cancel(true); // this will kill the running thread
} catch (InterruptedException | ExecutionException e) {
    // the runnable exploded
}
Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • Just wonder, for `future.cancel(true)`, if the task is still running, I believe it will just do an interrupt. If the `Runnable` is a infinite loop with no logic that is aware of interruption, it will not be stopped. Is my understanding correct? – Adrian Shum Jan 06 '17 at 04:39
0

There are many solutions to this. Here is one of them:

public class Main {

    public static void main(String... args) {
        Thread thread = () -> {
            while(true) {
                // We call Thread.interrupted to check the interrupted status of the Thread.
                // This method also clears the interrupted status of the Thread.
                if(Thread.interrupted()) {
                    break;
                }
                // code...
            }
        }

        Thread timer = () -> {
            long start = System.currentTimeMillis();
            while(true) {
                long current = System.currentTimeMillis();
                if(current - start == 10_000){ // underscore for clarity
                    // This causes the thread to interrupt. The next pass
                    // in our loop in "thread" will first check its interrupted status
                    // before continuing, and will break if the status is interrupted
                    thread.interrupt();
                    break;
                }
            }
        }

        // wrap in synchronized block to ensure both threads run simultaneously
        synchronized(Main.class) {
            thread.start();
            timer.start();
        }
    }
}

Explanation

In this solution, we use interrupts to make the while-loop in thread break. An interrupt stops normal thread operation at whatever point it is executing, but only if the thread calls a method that throws InterruptedException, and the thread returns (or breaks) in the catch block. Thread.interrupt() sets the interrupt status to interrupted, but does not actually interrupt the Thread from execution. The run method of thread is invoked again after being interrupted. Since we check Thread.interrupted() at the beginning of our loop, the loop will break when the loop is entered, and run exits and the Thread ceases to run. With Thread.interrupted(), you have to check, in the same Thread, for its interrupted status, and then choose what to do if the status is cleared or not.

As a side note, if our only branch (if-statement) in the loop checks for the interrupted state of that thread, then it might be easier to read and be more efficient if we simply declare our while-loop as while(!Thread.interrupted()). Otherwise it should be left as it is.

Other Solutions

Other solutions are illustrated by some other answers here, though some answer your whole problem in a different way. Some other solutions include using an AtomicBoolean as answered by Oliver Dain; another using a global lock object and volatile booleans or AtomicBoolean.

There are many solutions, but the interrupt solution seems to be the easiest and most convenient in most use-cases.

AMDG
  • 1,118
  • 1
  • 13
  • 29