Every object has an intrinsic lock that threads can acquire using synchronized. A thread calls wait when there isn't anything it can do until something changes (for instance, it might be trying to insert into a bounded queue that is currently full), it calls wait on the object whose lock it acquired with synchronized. When the main thread calls b.wait(), it means the main thread is the one that is going dormant.
When the code has wait commented out the ThreadB thread is still in the process of starting and the main thread can take the lock, then release the lock and print total before ThreadB can acquire the lock, at which time total is still 0. Technically there is a race and it's not guaranteed which thread goes first but the main thread has a good head start.
When the code uses wait then the main thread acquires the lock on ThreadB (again getting there ahead of ThreadB), then waits until it receives a notify. It happens that when a thread terminates it causes the scheduler to notify any threads in its waitset, so when threadB finishes it causes the main thread to wake up and proceed from there. Since ThreadB is done total is 10 by the time the main thread gets around to printing it.
If somehow the main thread did not get the lock before ThreadB, then (since ThreadB holds onto the lock for the whole time it is running) it couldn't acquire the lock until after ThreadB was finished. That would mean it wasn't in the waitset at the time the dying thread sent its notification, but would wait later, and there wouldn't be any notification to wake it up and it would hang.
This kind of problem - races that result in lost notifications and threads hanging - can happen when wait/notify is misused. For the right way to use wait and notify read https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html.
If you specifically want a thread to wait for another thread to finish, Thread has an instance method called join that is used for that, which the main thread would call here like
b.join();
BTW the notify-on-termination behavior is documented in the api doc for java.lang.Thread#join, it says:
This implementation uses a loop of this.wait calls conditioned on this.isAlive. As a thread terminates the this.notifyAll method is invoked. It is recommended that applications not use wait, notify, or notifyAll on Thread instances.
Note the warning against synchronizing on Thread objects. It doesn't play nice with JDK code like join that is locking on threads.
b.wai()doesn't mean that threadbwill wait, but the thread that is calling thewaitmethod. The calling thread (here it's the main thread) will wait for a notification from thread b.wait()on instances ofThread, nor callsynchronizedon Thread. It can cause deadlocks.Object.wait()and the Guarded Blocks tutorial to learn whatb.wait()actually means and, for how thewait(),notify(), andnotifyAll()methods are supposed to be used.