javaconcurrencysynchronized

Why are all my threads using the same lock?


I recently came across this code snippet

class Counter2 implements Runnable{

    private int value = 0;

    
    private Integer lock = 0;

    

    public void increment(){
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        value++;
    }

    public void decrement(){
        value--;
    }

    public int getValue() {
        return value;
    }

    @Override
    public void run() {
        synchronized(lock) {
            this.increment();
            System.out.println(String.format("%s incremented value, value = %s", Thread.currentThread().getName(), this.value));
            this.decrement();
            System.out.println(String.format("%s decremented value, value = %s", Thread.currentThread().getName(), this.value));
        }
    }
}
public class Part8_synchronized_keyword_1 {
    
    public static void main(String[] args) {
        Counter2 counter = new Counter2();
        new Thread(counter,"Thread1").start();
        new Thread(counter,"Thread2").start();
        new Thread(counter,"Thread3").start();
        new Thread(counter,"Thread4").start();
        Counter2 counterB = new Counter2();
        new Thread(counterB, "ThreadB").start();

    }
}

I get the output as follows

Thread1 incremented value, value = 1
Thread1 decremented value, value = 0
ThreadB incremented value, value = 1
ThreadB decremented value, value = 0
Thread4 incremented value, value = 1
Thread4 decremented value, value = 0
Thread3 incremented value, value = 1
Thread3 decremented value, value = 0
Thread2 incremented value, value = 1
Thread2 decremented value, value = 0

Why is it that it seems that threadB is competing with thread1...4 for the same lock?. Because from my understanding the lock object will be different for counterB and counter


Solution

  • Your assumption that counter and counterB use different locks is wrong.

    Why: because you declared your lock as

    private Integer lock = 0;
    

    and the JVM caches Integer objects for all values from -128 to 127.

    Declare your lock as

    private Object lock = new Object();
    

    and only then will counter and counterB use different locks!


    Note the important distinction here between object references and the referenced objects:

    Both counter and counterB have a field lock, but these fields are not objects, they are merely references to some object.

    For

    private Integer lock = 0;
    

    that referenced "some object" is the same for counter and counterB (because the Java Language Specification requires this for boxing of int values in the range -128 to 127) and synchronization happens on the object, not on the reference.

    Excerpt from the Java Language Specification, Boxing Conversion:

    If the value p being boxed is the result of evaluating a constant expression (§15.29) of type boolean, byte, char, short, int, or long, and the result is true, false, a character in the range '\u0000' to '\u007f' inclusive, or an integer in the range -128 to 127 inclusive, then let a and b be the results of any two boxing conversions of p. It is always the case that a == b.