使用Integer静态对象作为锁时线程同步失效的原因
Integer as a Lock Causes IllegalMonitorStateException (and Fixes) Great question! Let's walk through exactly what's going wrong with your original code, and why switching to a static Object lock fixes the issues you're seeing.
The Root Cause: Integer's Immutability Breaks Lock Consistency
The core problem here is that Integer is an immutable class, and you're using it both as your synchronization lock and your counter variable. This combination creates a hidden issue where your lock object gets silently replaced mid-execution, triggering IllegalMonitorStateException and breaking thread coordination.
Let's break down the step-by-step issue in your code:
- Lock Acquisition: When a thread enters
synchronized(counter), it acquires the lock on the specificIntegerinstance thatcounterpoints to (initially the instance holding the value2). counter++Changes the Lock:counter++doesn't modify the existingIntegerobject (immutable classes can't be changed after creation). Instead, it does something likecounter = Integer.valueOf(counter.intValue() + 1)—this creates a newIntegerinstance (or reuses a cached one, but the reference still changes) and updates thecountervariable to point to this new object.notify()Fails Due to Mismatched Lock: When you callcounter.notify()later, your thread still holds the lock on the originalIntegerinstance (the one with value2), butcounternow points to a completely different object (e.g., value3). Since you don't hold the lock on this new object, callingnotify()on it throwsIllegalMonitorStateException.
This lock mismatch also explains the unstable output and missing "Complete" message: the broken wait/notify logic means threads can get stuck waiting forever, so your t1.join() and t2.join() might never complete.
Why a Static Object Lock Works
When you switch to using static Object lock as your synchronization target:
- The
lockvariable always points to the sameObjectinstance—you never modify this reference, only update the separatecountervariable. - Every time you enter
synchronized(lock), you're acquiring the lock on the exact same object. Callinglock.wait()andlock.notify()is always valid because your thread holds the lock on that object. - Thread coordination works as expected: even threads wait and wake each other correctly, both threads finish their loops, and your main thread's
join()calls complete, so "Complete" prints reliably.
A Quick Note on Integer Caching
You might wonder: since Integer caches values from -128 to 127, does that change anything? Nope—even if counter++ reuses a cached instance, the counter reference still switches to a different object. The lock is tied to the object instance, not its value, so switching references breaks the lock consistency regardless of caching.
Fix Recommendations
Beyond using a static Object lock, here are a couple other approaches:
- Separate Counter and Lock: Always keep your counter variable and synchronization lock as separate entities. Never use an immutable object as a lock unless you can guarantee its reference will never change.
- Use
AtomicIntegerfor Counting: If you want to avoid manual synchronization for the counter itself, useAtomicInteger—but you'll still need a separate lock object for the wait/notify logic, since atomic operations don't handle thread waiting.
内容的提问来源于stack exchange,提问作者kalpesh

