One solution would be to use an ExecutorService to launch the threads, then when it is time to shutdown you tell the ExecutorService to shutdown(), then use and combination of isTerminated() and awaitTermination(time,units) to determine when all the threads are dead.
You could probably do something similar if you generate your Threads using a single ThreadGroup, then loop a wait/sleep step until the ThreadGroup's activeCount() reaches zero.
Finally, you could have a 'listener' type protocol. Wrap all your tasks in a Runnable. The first thing the run() method does is register itself in some listener, and the last thing it does is tell the listener it is complete. Your contextDestroyed would then wait on the listener to report that all the Threads that have been registered have completed. This isn't any different, really, than the ExecutorService strategy, except you need
Java 1.5+ to use Executors, so if your
servlet container doesn't yet use Java 1.5 you would need to 'roll your own.'