The main reason for job.waitForCompletion exists is that its method call returns only when the job gets finished, and it returns with its success or failure status which can be used to determine that further steps are to be run or not.
Actually, the files are split into blocks and each block is executed on a separate node. All the map tasks run in parallel and they are fed to the reducer after they are done. There is no question of synchronization as you would think about in a multi-threaded program. In a multi-threaded program, all the threads are running on the same box and since they share some of the data you have to synchronize them. But here threads are not involved unless you use threads to submit jobs in parallel and wait for their completion. For that, you have to use a job class instance per thread.