In my particular case I know that all of my Futures return the same kind of data, specifically an Iterator, though it doesn't matter what your Future returns for this to work.
The code goes something like this (example code so all the hardcoded values are dummy values)...
The code sets up a LinkedList to keep track of all of the Future instances that are running. Then 10 Future instances (MyFuture, implementation not shown) are submitted to an executor service to run (not showing creation of this service). After all of that there is a simple loop that uses the Iterator from the LinkedList to loop through all running Future instances, calls get() and then removes that Future instance from the list.
The trick is in the get() call because it blocks the current thread until the Future has completed its task. If that Future has already completed running then get() simply returns the last result. At this point calling remove() on the Iterator takes the Future at the start of the list out of the list which makes it possible to move onto the next Future. Eventually the list will be reduced to zero and the loop will terminate.
After the loop completes, it is guaranteed that all of the Future instances have completed their execution.
In my actual code I do some additional result capture in the while loop. I didn't show that above since that's very implementation specific.