How to know if other threads have finished?
You should really prefer a solution that uses java.util.concurrent
. Find and read Josh Bloch and/or Brian Goetz on the topic.
If you are not using java.util.concurrent.*
and are taking responsibility for using Threads directly, then you should probably use join()
to know when a thread is done. Here is a super simple Callback mechanism. First extend the Runnable
interface to have a callback:
public interface CallbackRunnable extends Runnable {
public void callback();
}
Then make an Executor that will execute your runnable and call you back when it is done.
public class CallbackExecutor implements Executor {
@Override
public void execute(final Runnable r) {
final Thread runner = new Thread(r);
runner.start();
if ( r instanceof CallbackRunnable ) {
// create a thread to perform the callback
Thread callerbacker = new Thread(new Runnable() {
@Override
public void run() {
try {
// block until the running thread is done
runner.join();
((CallbackRunnable)r).callback();
}
catch ( InterruptedException e ) {
// someone doesn't want us running. ok, maybe we give up.
}
}
});
callerbacker.start();
}
}
}
The other sort-of obvious thing to add to your CallbackRunnable
interface is a means to handle any exceptions, so maybe put a public void uncaughtException(Throwable e);
line in there and in your executor, install a Thread.UncaughtExceptionHandler to send you to that interface method.
But doing all that really starts to smell like java.util.concurrent.Callable
. You should really look at using java.util.concurrent
if your project permits it.
Solution using CyclicBarrier
public class Downloader {
private CyclicBarrier barrier;
private final static int NUMBER_OF_DOWNLOADING_THREADS;
private DownloadingThread extends Thread {
private final String url;
public DownloadingThread(String url) {
super();
this.url = url;
}
@Override
public void run() {
barrier.await(); // label1
download(url);
barrier.await(); // label2
}
}
public void startDownload() {
// plus one for the main thread of execution
barrier = new CyclicBarrier(NUMBER_OF_DOWNLOADING_THREADS + 1); // label0
for (int i = 0; i < NUMBER_OF_DOWNLOADING_THREADS; i++) {
new DownloadingThread("http://www.flickr.com/someUser/pic" + i + ".jpg").start();
}
barrier.await(); // label3
displayMessage("Please wait...");
barrier.await(); // label4
displayMessage("Finished");
}
}
label0 - cyclic barrier is created with number of parties equal to the number of executing threads plus one for the main thread of execution (in which startDownload() is being executed)
label 1 - n-th DownloadingThread enters the waiting room
label 3 - NUMBER_OF_DOWNLOADING_THREADS have entered the waiting room. Main thread of execution releases them to start doing their downloading jobs in more or less the same time
label 4 - main thread of execution enters the waiting room. This is the 'trickiest' part of the code to understand. It doesn't matter which thread will enter the waiting room for the second time. It is important that whatever thread enters the room last ensures that all the other downloading threads have finished their downloading jobs.
label 2 - n-th DownloadingThread has finished its downloading job and enters the waiting room. If it is the last one i.e. already NUMBER_OF_DOWNLOADING_THREADS have entered it, including the main thread of execution, main thread will continue its execution only when all the other threads have finished downloading.
There are a number of ways you can do this:
- Use Thread.join() in your main thread to wait in a blocking fashion for each Thread to complete, or
- Check Thread.isAlive() in a polling fashion -- generally discouraged -- to wait until each Thread has completed, or
- Unorthodox, for each Thread in question, call setUncaughtExceptionHandler to call a method in your object, and program each Thread to throw an uncaught Exception when it completes, or
- Use locks or synchronizers or mechanisms from java.util.concurrent, or
- More orthodox, create a listener in your main Thread, and then program each of your Threads to tell the listener that they have completed.
How to implement Idea #5? Well, one way is to first create an interface:
public interface ThreadCompleteListener {
void notifyOfThreadComplete(final Thread thread);
}
then create the following class:
public abstract class NotifyingThread extends Thread {
private final Set<ThreadCompleteListener> listeners
= new CopyOnWriteArraySet<ThreadCompleteListener>();
public final void addListener(final ThreadCompleteListener listener) {
listeners.add(listener);
}
public final void removeListener(final ThreadCompleteListener listener) {
listeners.remove(listener);
}
private final void notifyListeners() {
for (ThreadCompleteListener listener : listeners) {
listener.notifyOfThreadComplete(this);
}
}
@Override
public final void run() {
try {
doRun();
} finally {
notifyListeners();
}
}
public abstract void doRun();
}
and then each of your Threads will extend NotifyingThread
and instead of implementing run()
it will implement doRun()
. Thus when they complete, they will automatically notify anyone waiting for notification.
Finally, in your main class -- the one that starts all the Threads (or at least the object waiting for notification) -- modify that class to implement ThreadCompleteListener
and immediately after creating each Thread add itself to the list of listeners:
NotifyingThread thread1 = new OneOfYourThreads();
thread1.addListener(this); // add ourselves as a listener
thread1.start(); // Start the Thread
then, as each Thread exits, your notifyOfThreadComplete
method will be invoked with the Thread instance that just completed (or crashed).
Note that better would be to implements Runnable
rather than extends Thread
for NotifyingThread
as extending Thread is usually discouraged in new code. But I'm coding to your question. If you change the NotifyingThread
class to implement Runnable
then you have to change some of your code that manages Threads, which is pretty straightforward to do.
Do you want to wait for them to finish? If so, use the Join method.
There is also the isAlive property if you just want to check it.