Ok let's look at the various features you mentioned:
1.
for (int ii=0; ii<NUM_EVENTS; ++ii) { synchronized( monitor) { synchronized(cnt) {
First, the monitor object is distributed between threads, so getting a lock on it (i.e. synchronization) ensures that the code inside the block will be executed only by one thread at a time. Thus, 2 synchronized inside the external are not needed, the code is protected in any case.
2.
for (int ii=0; ii<NUM_EVENTS; ++ii) { synchronized( monitor) { monitor.add(cnt); } synchronized(cnt) { cnt++;
Okay, this is a little complicated. cnt is an Integer object, and Java does not allow the Integer object to be modified (integers are immutable), although the code assumes that this is what happens here. But what will happen is that cnt ++ will create a new Integer with a value of cnt + 1 and override cnt. This is what the code actually does:
synchronized(cnt) { Integer tmp = new Integer(cnt + 1); cnt = tmp; }
The problem is that although one thread will create a new cnt object, all other threads are waiting to get a lock on the old one. Now the thread releases the old cnt and then tries to get the lock of the new cnt object and get it, and the other thread gets the lock of the old cnt object. Suddenly, in a critical section, there are 2 threads executing the same code and causing a race condition. This is where the wrong results happen.
If you delete the first synchronized block (the one with the monitor), then your result will become even more incorrect, because the chances of a race increase.
In general, you should try to use synchronization only for the final variables so that this does not happen.
source share