6

I have an application that starts a few threads, eventually a thread may need to exit the entire application, however other threads may be in the middle of a task so I'd like to let them continue their current loop before exiting.

In the example below Thread2 has no idea when Thread1 is trying to exit, it simply forces everything to finish right away.

How can I let Thread2, 3 & 4 etc. finish up their loops before shutting down?

Edit: To address the duplicate question concerns: this varies from the typical situation in that the parent class cannot be responsible for puppeteering the shut downs, any of the individual threads must be able to initiate the shut down.

Edit2: I've also left an answer with what I ended up doing which is an implementation of the accepted answer.

class Scratch {
    public static void main(String[] args) {
        Thread Task1 = new Thread(new Task1());
        Task1.start();

        Thread Task2 = new Thread(new Task2());
        Task2.start();

        // ... more threads
    }

    public class Task1 implements Runnable {
        public void run() {
            while (true) {
                // ...
                System.exit(0);
                // ...
            }
        }
    }

    public class Task2 implements Runnable {
        public void run() {
            while (true) {
                // ...
                // I need to know about System.exit(0) to exit my loop
                // ...
            }
        }
    }
}
Royal Wares
  • 1,192
  • 10
  • 23
  • 1
    check https://stackoverflow.com/questions/10961714/how-to-properly-stop-the-thread-in-java – Kevorkian Sep 20 '18 at 14:57
  • @Kevorkian it's not quite what I was looking for as that has a 'puppeteer' type relationship where the parent class is in charge of shutting down the app, whereas I need any of the threads to be able to call for a shut down. – Royal Wares Sep 20 '18 at 15:12

6 Answers6

3

You can use a volatile boolean variable that all threads will constantly check. If one thread sets the value of that variable to false, all threads will see the new value and leave the while loop.

Explanation: read / write operations in volatile variables are atomic. Besides that, the value of volatile variables are not cached, so all threads see the same value.

class Scratch {

    private static volatile boolean isRunning = true;

    public static void main(String[] args) {
        Thread Task1 = new Thread(new Task1());
        Task1.start();

        Thread Task2 = new Thread(new Task2());
        Task2.start();

        // ... more threads
    }

    public class Task1 implements Runnable {
        public void run() {
            while (isRunning) {
                // ...
                isRunning = false; // let's stop all threads
                // ...
            }
        }
    }

    public class Task2 implements Runnable {
        public void run() {
            while (isRunning) {
                // ...
                // I need to know about System.exit(0) to exit my loop
                // ...
            }
        }
    }
}
HugoTeixeira
  • 4,674
  • 3
  • 22
  • 32
  • I think this solution would work for my case, however as each task can take a varying amount of time to finish, is there a way to check from the main() if all threads have finished? – Royal Wares Sep 20 '18 at 15:05
  • Oh, maybe I could also have a volatile int, +1 for each new thread I make, -1 as they finish. Then have a separate 'control' thread to monitor the count and boolean, when it's at false and zero we can call System.exit(0); – Royal Wares Sep 20 '18 at 15:10
  • You have to be careful in this case because incrementing/decrementing a `volatile int` variable isn't atomic because you will have two separate operations (one read and one write). You could use an `AtomicInteger` in that case. – HugoTeixeira Sep 20 '18 at 15:13
  • Thanks, I've just read up on the difference between the two. This now forms a complete solution for me. – Royal Wares Sep 20 '18 at 15:16
1

Creating thread by yourself is considered as "bad practice", you should think about using an ExecutorService. The synchronization should be done via interrupting the threads.

class Scratch {
    private final ExecutorService executorService;
    public Scratch(ExecutorService es) {
       this.executorService = es;
    }
    /** Convience constructor */
    public Scratch() {this(Executors.newCachedThreadPool());}

    public class Task1 implements Callable<Void> {
        public Void call() throws Exception {
            while(true) {
                ...
                executorService.shutdownNow(); // interrupt all running threads
                                               // (including Task1) started by
                                               // given executor service
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
            return null;
        }
     }

     public class Task2 implements Callable<Void> {
         public Void call() throws Exception {
             while(true) {
                 // check if the thread was interrupted, if so throw Exception
                 if (Thread.interrupted())
                     throw new InterruptedException();
             }
             return null;
         }
     }

     public static void main(String ... args) {
         Scratch s = new Scratch();
         s.executorService.submit(new Task1());
         s.executorService.submit(new Task2());
     }
 }
Torsten Fehre
  • 567
  • 2
  • 7
  • This looks like it kills the thread immediately, is that the case? – Royal Wares Sep 20 '18 at 15:51
  • No, this will not kill the thread. According to the documentation the call of ``shutdownNow`` on ExecutorService will interrupt all currently running threads (at least in the default implementations), which will set the interrupted flag). The interruption itself will only occur if the thread will itself check for interruption and then throw the exception. – Torsten Fehre Sep 21 '18 at 07:32
1

Besides the method achieved by using a volatile boolean variable, which is a great one, you could also consider using listeners. They are commonly used through out the Android API.

First, create the interface that defines a listener. Here is an example:

public interface TaskListener {
    public void onFinish();
}

Now, implement this interface on the classes that you want to be notified of your task's finishing. Look below:

public class Task2 implements Runnable, TaskListener { 
    public void run() { 
        while (!Thread.currentThread().isInterrupted()) { 
            //... 
        } 
    }

    public void onFinish() {
         //perform your exit operations here.
         Thread.currentThread().interrupt();
    } 
}

Now, prepare your main task's class to receive listeners. Take a look:

public class Task1 implements Runnable { 
    private ArrayList<TaskListener> listeners = new ArrayList<>();

    public void run() { 
        while (true) { 
            // ... 
            this.finish();
            // ... 
        } 
    } 

    public void addListener (TaskListener listener) {
        this.listeners.add(listener);
    } 

    private void finish() {
        for(TaskListener listener: this.listeners) {
             listener.onFinish();
        } 
    } 
 }

With every thing set up, now it's easy to listen for your task's finishing. Look below:

public static void main(String[] args) { 
    Thread task1 = new Thread(new Task1()); 
    Thread task2 = new Thread(new Task2()); 

    task1.addListener(task2);
    task1.start();
    task2.start();
} 

Done! Now, everytime task1.finish() is called, all it's listeners (only task2 in this example) are notified (their onFinish method is called).

Note: it's not necessary to implement TaskListener in all of your tasks if you don't want to. Here is an example using a lambda expression:

public static void main(String[] args) { 
    Thread task1 = new Thread(new Task1()); 
    Thread task2 = new Thread(new Task2()); 

    task1.addListener(() -> {
        //this entire block will be called when task1 finishes
        task2.interrupt();
    });

    task1.start();
    task2.start();
} 

Note2: I wrote this answer on my phone. Although I revised the code several times and it seems OK, I won't be able to test it until I get home.

Talendar
  • 1,841
  • 14
  • 23
  • this one took me a little longer to digest, I like the approach but potentially not for my use case - since each new task would require me to retrospectively register a new listener on each existing task. However I will keep this in my mind as I can see it having a lot of use further in my project. Thanks. – Royal Wares Sep 21 '18 at 07:16
  • I'm glad you liked it! Note that by using the *lambda expression* example you don't have to implement `TaskListener` on your classes. Thus, with only one listener you can handle the "finishing operations" of all your tasks. Cheers! – Talendar Sep 21 '18 at 10:55
0

You can't do that easily.

First of all System.exit(0) exits the JVM, and it doesn't care about threads.

What you would have to do:

  • define your own framework to manage threads
  • also derive an "architecture" for your threads. Anything that is supposed to be controlled by your framework would need A) means to query "how much longer will you run?" and B) "prepare for going down"

And of course: in the real world, there might many threads running in your application that you don't even know of (for example when you used someCollection.parallelStream() somewhere: that uses a JVM wide thread pool with threads out of your control).

Some middle ground would be a "thread manager" component. Threads that do not want to be killed have to register with that manager.

But still: even that is hard to get right. What happens for example when some threads are in a deadlock? Or waiting for something "outside" of the JVM to happen? It takes a lot of effort to figure that threads are really progressing, too.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • My use case is a lot less involved than this but I take the points you make and will consider them as my app grows. Currently it's closer to the barebones example I provided than a fully fledged app. Each thread also has relatively short intervals, it can finish a loop in about 3s. – Royal Wares Sep 20 '18 at 15:06
0

As suggested by @HugoTeixeira I ended up using volatile & atomic-integers to keep track of the threads. I also made a control thread to periodically check the values and decide if it's time to exit.

class Scratch {
    public static volatile boolean isRunning = true;
    public static AtomicInteger threadCount = new AtomicInteger();

    public static void main(String[] args) {
        Thread controlThread = new Thread(new ControlThread());
        controlThread.start();

        Thread Task1 = new Thread(new Task1());
        Task1.start();
        if(Task1.isAlive()) { threadCount.incrementAndGet(); }

        Thread Task2 = new Thread(new Task2());
        Task2.start();
        if(Task2.isAlive()) { threadCount.incrementAndGet(); }

        // ... more threads
    }
}

Task threads:

public class Task1 implements Runnable {
    public void run() {
        while (isRunning) {
            // ...
            isRunning = false; // let's stop all threads
            // ...
        }
     }
  }

Control Thread:

import static Scratch.isRunning;
import static Scratch.threadCount;

public class ControlThread implements Runnable {
    public void run() {
        while(true) {
            try {
                // Check if we have stopped running the app AND all threads have finished up.
                if(!isRunning && threadCount.get() == 0) {
                    // Safely close the app
                    System.exit(0);
                }
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                // Ignore exceptions
            }
        }
    }
}
Royal Wares
  • 1,192
  • 10
  • 23
  • I'm glad you could make it work! Have you checked my method using listeners? It can also come in handy. – Talendar Sep 20 '18 at 19:59
0

If the intention is to wait all Thread to complete execution. Consider using ExecutorService and Future. The Sketch below represents general idea of implementing it.

final ExecutorService pool = Executors.newCachedThreadPool();

Future<?> task1 = pool.submit(() -> {
    //do loop task1
});

Future<?> task2 = pool.submit(() -> {
    //do loop task2
});

while(!task1.isDone() && !task2.isDone()){
   //waits to complete
}

After all tasks completed, shutdown the executor using pool.shutdown(). However to this is just simple example. Refer ExecutorService javadoc for more details.

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html

Awan Biru
  • 373
  • 2
  • 10