Since the last task doesn't know that it's the last, I actually don't think it's possible to have this work 100% correctly without recording both when tasks launch and when they complete.
If memory serves me right, the getQueue() method returns a queue containing only tasks that are still waiting to be executed, not ones that are currently running. Furthermore, getCompletedTaskCount() is approximate.
The solution I'm pondering goes something like this, using an atomic counter like in Eng.Fuoad's answer and a Condition for signaling the main thread to wake up (pardon the shortcuts for simplicity):
public class MyThreadPoolExecutorState { public final Lock lock = new ReentrantLock(); public final Condition workDone = lock.newCondition(); public boolean workIsDone = false; } public class MyThreadPoolExecutor extends ThreadPoolExecutor { private final MyThreadPoolExecutorState state; private final AtomicInteger counter = new AtomicInteger(0); public MyThreadPoolExecutor(MyThreadPoolExecutorState state, ...) { super(...); this.state = state; } protected void beforeExecute(Thread t, Runnable r) { this.counter.incrementAndGet(); } protected void afterExecute(Runnable r, Throwable t) { if(this.counter.decrementAndGet() == 0) { this.state.lock.lock(); try { this.state.workIsDone = true; this.state.workDone.signal(); } finally { this.state.lock.unlock(); } } } } public class MyApp { public static void main(...) { MyThreadPoolExecutorState state = new MyThreadPoolExecutorState(); MyThreadPoolExecutor executor = new MyThreadPoolExecutor(state, ...); // Fire ze missiles! executor.submit(...); state.lock.lock(); try { while(state.workIsDone == false) { state.workDone.await(); } } finally { state.lock.unlock(); } } }
It could be a little more elegant (maybe just provide a getState() in your thread pool executor or something?), but I think it should get the job done. It's also untested, so implement at your own peril...
It is worth noting that this solution will definitely fail if there are no tasks to be executed -- it'll await the signal indefinitely. So don't even bother starting the executor if you have no tasks to run.