ThreadManager Design Points
I found many subtle and interesting design points in writing ThreadManager, and probably not all of them.
For instance, the major service provided by the ThreadManager is keeping track of each thread it starts so that they can all be stopped at some later time. This requires nothing more than keeping an accurate list of interruptible objects running on various threads. The list part is easy; the accurate part takes a bit of effort.
Each Interruptible is added to the list of running objects before its assigned Thread is started. This guarantees that once ThreadManager.run() is called for some Interruptible, both run() and interrupt() are called on it. It also makes it possible for interrupt() to be called first.
The second half of this problem is removing each Interruptible from the list when it exits, which happens when the run() method returns. This is handled by scheduling not the Interruptible itself but a private class called ManagedRunnable. ManagedRunnable has a run() method that wraps the call to Interruptible.run() in a try/finally block. The finally clause is invoked when run() returnswhether normally or with an exceptionand removes the Interruptible from the list.
The kind of interrupt required varies, depending on what the code in the thread is doing. The Thread.interrupt() method isn't always appropriate. For example, while it will interrupt a thread waiting on Object.wait(), it won't interrupt a thread waiting on ServerSocket.accept(). The interrupt() method implementationwhich is always called from a thread other than the one on which run() is calledmust take appropriate action to interrupt the thread, whatever that might be. My prototype called for three different strategies.
My prototype used SWT for the UI. The application bundle launched a thread on which a loop polled and processed the SWT event queue. The interrupt method for this thread made use of the SWT Display.asyncExec() call to post a "quit" event back to the UI thread, causing that loop to exit. Swing provides similar facilities. Worst case, you could implement your own mechanism for passing the request between threads and check it in the event loop. (In that case, however, you might need a timeout on the call to poll for an event.)
Threads waiting on I/O are even easier; typically, simply closing the stream or socket on which they are waiting is sufficient. The close will cause the read(), accept(), or other call to return with an error. The thread performing the I/O simply needs to exit when this error occurs.
Worker threads performing computation can be the trickiest because there is not a good mechanism for interrupting CPU-bound work from another thread. The only option here is to have the thread periodically check to see if an interrupt bit has been set. It shouldn't do that too often or the overhead will take a toll, but it must do it frequently enough to keep the interrupt responsive.