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
// ...
}
}
}
}
exit() kills all the other threads. What you wrote is correct but it doesn't have anything to do with thread interruption. If you interrupt a thread, it will not just stop. It is up to the code running in that thread to decide what happens when it is interrupted.
One method is for a thread to call interrupt on the target thread. The target thread should periodically call Thread. interrupted and if it returns true terminate gracefully. Another approach is to use a volatile boolean flag which a thread sets to true to terminate and the target thread checks the flag periodically.
thread. join() waits for the thread to finish before it continues. So the sub-thread will wait for each of the threads that it spawned to finish and then it will finish. The main thread will wait for the sub-thread to finish and will then finish last.
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
// ...
}
}
}
}
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());
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With