No es necesario porque está esperando la señal de "hecho". Esa memoria vacía de una manera que todos los valores del hilo esperado se vuelven visibles para el hilo principal.
Sin embargo, puede probarlo fácilmente, hacer dentro del
run method a computation that takes several (millions) steps and don't get optimized by the compiler, if you see a value different than from the final value that you expect then your final value was not already visible to main thread. Of course here the critical part is to make sure the computation doesn't get optimized so a simple "increment" is likely to get optimized. This in general is usefull to test concurrency where you are not sure if you have correct memory barriers so it may turn usefull to you later.
synchronized is not needed around
System.out.println(number);, but not because the
PrintWriter.println() implementations are internally synchronized or because by the time
doneSignal.await() unblocks all the worker threads have finished.
synchronized is not needed because there's a happens-before edge between everything before each call to
doneSignal.countDown and the completion of
doneSignal.await(). This guarantees that you'll successfully see the correct value of