Why Does synchronized Fail on Integer Locks? Deep Dive into Java Concurrency
This article explores why using an Integer as a lock can cause synchronized to malfunction, demonstrates the issue with a ticket‑selling example, analyzes thread dumps and JVM bytecode, and presents reliable alternatives such as class‑level locks or map‑based lock objects.
The author encountered a puzzling synchronized issue during a Java interview and decided to share a reproducible example.
public class SynchronizedTest {
public static void main(String[] args) {
Thread why = new Thread(new TicketConsumer(10), "why");
Thread mx = new Thread(new TicketConsumer(10), "mx");
why.start();
mx.start();
}
}
class TicketConsumer implements Runnable {
private volatile static Integer ticket;
public TicketConsumer(int ticket) {
this.ticket = ticket;
}
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName() + "开始抢第" + ticket + "张票,对象加锁之前:" + System.identityHashCode(ticket));
synchronized (ticket) {
System.out.println(Thread.currentThread().getName() + "抢到第" + ticket + "张票,成功锁到的对象:" + System.identityHashCode(ticket));
if (ticket > 0) {
try {
//模拟抢票延迟
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了第" + ticket-- + "张票,票数减一");
} else {
return;
}
}
}
}
}The program simulates two threads competing for ten tickets. The expected result is that each ticket is taken by only one thread, but the log shows both threads acquiring the same ticket (e.g., ticket 9) and reporting identical lock identities.
Thread‑dump analysis reveals that during the first round both threads contend for the same monitor (lock address 0x000000076c07b058). In the second round each thread holds a different monitor (0x000000076c07b058 and 0x000000076c07b048), so synchronized does not enforce mutual exclusion.
The root cause is the lock object changing because the ticket variable is an Integer. Autoboxing creates new Integer instances when the value leaves the JVM’s cache range (‑128 to 127). Even within the cache range, each decrement creates a distinct object, so the monitor used by synchronized(ticket) can change between iterations.
Fixes include using a stable lock such as the class object: synchronized (TicketConsumer.class) { ... } or employing a map to canonicalize lock objects, e.g., a ConcurrentHashMap<Integer, Object> with putIfAbsent to ensure a single instance per key.
The article also references a similar StackOverflow question, noting that even though integers within the cache share the same object, using them as locks is fragile and discouraged by experts such as Brian Goetz.
In summary, do not use Integer as a lock object ; instead choose a constant object, a class literal, or a dedicated lock stored in a concurrent map to guarantee a single monitor across threads.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
