3

I have the following class, I usually run about 10 threads of it

public class MyClass implements Runnable {    
  private volatile Device device = null;

  public MyClass(Device device) {
    this.device = device;
  }

  @Override
  public void run() {
    while (true) {  // <--- I do know that the "true" has to be changed to a Boolean
      try {
        Worker worker = new Worker();
        worker.work();
        System.out.println("Waiting 6 seconds!");
        Thread.sleep(6 * 1000);
        System.out.println("------------------------------------");
      } catch (Exception e) {
        e.printStackTrace();
      }
    }

    System.out.println("Thread in program ended!");
  }
}

and in my main I start the threads like this

for (int i = 0; i < 2; i++) {
  (new Thread(new MyClass())).start();
}

This is a console based program. What is the most reliable way to end the program? I think the best way would be to change while (true) to while (Boolean) and somehow change that Boolean for all threads, then when the loop ends, the program will end gracefully.

Arya
  • 8,473
  • 27
  • 105
  • 175

3 Answers3

0

Here i'm ending it by waiting for a user input but you can change it to fire the stop method from anywhere

    public static void main(String[] args) {

    List<MyClass> myThreads = new ArrayList<>();
    for (int i = 0; i < 2; i++) {
        MyClass myClass = new MyClass();
        Thread t = new Thread(myClass);
        t.start();
        myThreads.add(myClass);
    }
    Scanner in = new Scanner(System.in);
    in.next();

    for(MyClass t : myThreads){
        t.stop();
    }

}

class MyClass implements Runnable {

private Boolean flag;

public MyClass() {
    this.flag = true;
}

@Override
public void run() {
    while (flag) {  // <--- I do know that the "true" has to be changed to a Boolean
        try {
            System.out.println("Waiting 6 seconds!");
            Thread.sleep(6 * 1000);
            System.out.println("------------------------------------");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    System.out.println("Thread in program ended!");
}

public void stop(){
    this.flag = false;
} }
Turan
  • 302
  • 2
  • 9
  • thanks, I will implement it soon. Shouldn't `private Boolean flag;` be `volatile` ? – Arya Apr 20 '18 at 23:26
  • Because each thread has its own flag you don't really need it to be volatile. Ofcourse it's only a small performance hit to make it volatile but not really necessary – Turan Apr 20 '18 at 23:31
  • Thanks, but I did change the last part to this ` while (true) { Scanner scanner = new Scanner(System.in); String userInput = scanner.nextLine(); System.out.println("Got input!"); if (userInput.equalsIgnoreCase("q")) { for (Sms t : myThreads) { t.stop(); } } }` – Arya Apr 20 '18 at 23:46
  • Several problems with this solution. First off it wont tell you if the thread actually succeeds at stopping. If there is a bug in a thread then it willhang your program forever. Second you are catching all exceptions. Aside from being bad practice this is a great way to introduce bugs that will overload the cpu. Imagine if an exception is thrown fromt he print line after it printed a few characters. The sleep would never be reached and the program would dump text as quick as it can to the screen forever and ping the cpu. – Jeffrey Phillips Freeman Apr 20 '18 at 23:55
  • 1
    Actually you should ALWAYS catch "Exception" a the top level of a thread, this is important because the VM will eat exceptions thrown by your thread invisibly which can cost days of work (Someone didn't catch/log Exception causing ME to spend days of work finding what was going on, twice!). Remember to always catch the most specific exception at a low level/near the exception but your outside loops (main loop and thread loops) should catch "Exception" and at least log it (Like in this answer). You can catch more types before "Exception" and react differently, but at least catch and log! – Bill K Apr 21 '18 at 00:03
  • 1
    @BillK If you wish to catch exceptions at the top level of a thread it should ONLY be done for logging purposes. You print the stacktrace then ALWAYS rethrow. You should never eat a runtime exception. – Jeffrey Phillips Freeman Apr 21 '18 at 00:08
  • You must always catch for logging purposes unless you replaced the default exception handler, that is absolute. In our permanent loops we tend to catch and continue which actually recovers in a lot of cases (More often than not for us), but that depends on your design. If your system doesn't have to stay stable then just letting a top-level thread exit after logging might be acceptable just so long as you at least log. If you are letting a high-level/permanent thread die like that and you don't catch/continue you might as well shut the system down since you've just lost an important function – Bill K Apr 23 '18 at 16:51
  • 1
    @JeffreyPhillipsFreeman Actually your point about dumping alarm text to the console is excellent, java can actually hang a console that way. Since you noticed this you should simply prevent it next time by a delay after the exception prints or using a logfile for exceptions. It took me a few days to recognize just what you were saying because I always use log files now... Good advice though--Be careful of spamming the console (in particular in Windows!) – Bill K Apr 24 '18 at 16:37
  • @BillK yup catch log then rethrow is always fine for runtime exceptions. But as a rule runtime exceptions should always cause a thread to exit and as such should always make it to the top. – Jeffrey Phillips Freeman Apr 24 '18 at 17:10
  • @JeffreyPhillipsFreeman I've had runtime exceptions recover--such as when a filesystem was being re-mounted on my server. They would glitch for a few seconds then recover and work fine. Had I done what you suggest critical features would have been disabled by the thread exiting. My approach has been NEVER let exceptions get to the top, handle them in a reasonable way then continue with your thread or abort the program completely (meaning a safe shutdown including other threads).. Just letting the thread exit is never acceptable unless the thread was transient by nature or will be restarted. – Bill K Apr 24 '18 at 17:31
  • @BillK If an exception can be recovered from then it should be written as a checked exception. If you dealt with a recoverable runtime exception then you dealt with a broken API. For example that is why the java core library would throw an IOException in that case, a checked exception you can occasionally recover from. – Jeffrey Phillips Freeman Apr 24 '18 at 21:54
0

The easy way would be to store all your threads in a set and make loop joining them at the end.
Be aware that this is not the most ortodox neither the most efficient way to do this.

In your main:

HashSet<Thread> threads = new HashSet();

for (int i = 0; i < 2; i++) {
    Thread t = new Thread(new MyClass());
    threads.add(t);
    t.start();
}

for (Thread thread: threads) {
    thread.join();
}

some more material

Lucas Noetzold
  • 1,670
  • 1
  • 13
  • 29
0

The following code uses an executor service to fix the number of threads that run at any time, it provides a Future object that also tells you when your thread has shutdown gracefully. They share a shutdown object as well. This offers you a bit more flexibility as the executor service can let you decide how many threads run at any one time gracefully.

First lets created a shared shutdown object that will notify all the threads it is time to shut down. There will be one instance of this and each thread will have a copy.

    public static class Shutdown {
        private boolean running;

        public void shutdown() {
            this.running = false;
        }

        public boolean isRunning() {
            return running;
        }
    }

Next let me just create a dummy thread that does nothing more than sleep forever while it is running. Obviously you can simply replace this with your own thread to do something useful.

    public static class MyClass implements Runnable {
        final Shutdown shutdown;


        public MyClass(Shutdown shutdown) {
            this.shutdown = shutdown;
        }

        @Override
        public void run() {
            while (shutdown.isRunning()) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    System.out.println("Did not gracefully shut down");
                }
            }

            System.out.println("Thread in program ended!");
        }
    }
}

Now for the main class which will run everything, this is where the magic happens.

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

        //run exactly 10 threads at a time
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        //this is how we shut it down
        Shutdown globalShutdown = new Shutdown();

        //start up the 10 threads
        List<Future<?>> futures = new ArrayList<>();
        for(int i = 0; i< 10; i++)
            futures.add(executorService.submit(new MyClass(globalShutdown)));

        //gracefully shut them down
        globalShutdown.shutdown();

        try {
            //wait for them all to shutdown
            for(Future<?> future : futures)
                future.get();
        } catch (InterruptedException e) {
            throw new IllegalStateException("This should never happen");
        } catch (ExecutionException e) {
            throw new IllegalStateException("This should never happen");
        }

        //everything got shutdown!
    }

in practice however you probably also want to handle the case where your thread may not end gracefully due to a bug. Rather than stall forever you might want to add a timeout and if that timeout is exceeded then simply forcibly terminate all remaining threads. To do that replace the above try-catch block with this.

    try {
        //wait for them all to shutdown
        boolean timedout = false;
        for(Future<?> future : futures) {
            if( !timedout ) {
                try {
                    future.get(30, TimeUnit.SECONDS);
                } catch (TimeoutException e) {
                    timedout = true;
                }
            }

            if(timedout) {
                future.cancel(true);
            }
        }
    } catch (InterruptedException | ExecutionException e) {
        throw new IllegalStateException("This should never happen");
    }