4

I have a simple code snippet

public class ItemManager {

    private Integer itemCount = 0;

    public void incrementAndPrint() {
        synchronized(this) { 
            System.out.println(++itemCount + " ");
        }
    }

    public static void main(String[] args) {
        ItemManager manager = new ItemManager();
        ExecutorService executor = Executors.newFixedThreadPool(20);

        for (int i = 0; i < 10; i++) {
            executor.submit(manager::incrementAndPrint); 
        }
        executor.shutdown();
    }
}

that produces 1 2 3 4 5 6 7 8 9 10 as expected. I can also create another field with Object instance and lock on it

    private Integer itemCount = 0;
    private Object lock = new Object();

    public void incrementAndPrint() {
        synchronized(lock) {
            System.out.println(++itemCount + " ");
        }
    }

and it will also produce 1 2 3 4 5 6 7 8 9 10 as expected.

However, if I try to lock on the same object that I want to increment and print

    private Integer itemCount = 0;

    public void incrementAndPrint() {
        synchronized(itemCount) {
            System.out.println(++itemCount + " ");
        }
    }

the operation will stay atomic but the result is out of order: 2 1 3 4 5 6 7 8 9 10.

I know that synchronized(this) or synchronizing the whole method will fix all my problems. I just can't understand why I can lock on one field (Object lock), but can't lock on another (Integer itemCount)? Shouldn't everything inside the synchronized block be locked properly, regardless of what this object is, as long as it is a single object shared between all threads?

1
  • 1
    You do not want to synchronize on an Object that might be cached, like Integer. Never do that. Commented Jan 29, 2021 at 17:26

2 Answers 2

5

Integers in Java are immutable. When you call ++itemCount, you're in fact performing three operations: First, the Integer is unboxed to an int with the value of Integer. Then, this primitive int is incremented, and finally the incremented int is autoboxed back to an Integer. So in fact, you eventually have different Integer instances. Since you synchronize on different instances, the synchronization is meaningless, and you see the out-of-order printing.

Sign up to request clarification or add additional context in comments.

1 Comment

Mureinik, I see, so it basically creates several instances of Integer. Thank you!
2

Because when you do ++itemCount, it is equivalent to:

int old = itemCount.intValue();
itemCount = Integer.valueOf(old + 1);

This changes the object that the lock is using, hence the synchronization is no longer correct. This happens for primitive wrapper classes like Integer and Long.

You can see the difference if you replace the Integer with an AtomicInteger which can increment the same object:

private AtomicInteger itemCount = new AtomicInteger();

public void incrementAndPrint() {
    synchronized(itemCount) { 
        System.out.println(itemCount.getAndIncrement() + " ");
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.