The lock is on the object, not on the variable.
When a thread tries to enter a synchronized block it evaluates the expression in parens after the synchronized keyword in order to determine what object to acquire the lock on.
If you overwrite the reference to point to a new object, then the next thread that tries to enter the synchronized block acquires the lock on the new object, so it can be the case that two threads are executing code in the same synchronized block on the same object (the one that acquired the lock on the old object may not be done when the other thread starts executing the block).
For mutual exclusion to work you need the threads to share the same lock, you can't have threads swapping out the lock object. It's a good idea to have a dedicated object you use as a lock, making it final to make sure nothing changes it, like this:
private final Object lock = new Object();
This way, since the lock object isn't used for anything else, there isn't the temptation to go changing it.
Memory visibility does not seem relevant here. You don't need to take visibility into account when reasoning about how swapping the lock creates problems, and adding code to let the lock object change in a visible way doesn't help to fix the problem, because the solution is to avoid changing the lock object at all.
m
and another that obtains a lock on a newm
. For that very reason the lock object is usually separated from the objects that hold your data. – Calumniate