You may see something like this:

public class ValueHolder {
    private int a;
    private final Object mutex = new Object();

    public int getA() {
        return a;
    }

    public void increment() {
        synchronized (mutex) {
            a++;
        }
    }
}

And you start wondering: “Hey, why do we use synchronized (mutex), can’t we just use synchronized (this) and everything would work?” Let’s see this in a practical example. Multiple threads will update the state of our ValueHolder.

The thread that will increment our counter 1000 times:

public class UpdaterThread implements Runnable {
    
    private ValueHolder valueHolder;

    public UpdaterThread(ValueHolder valueHolder) {
        this.valueHolder = valueHolder;
    }

    @Override
    public void run() {
        for (int i = 0 ; i < 1000; i++) {
            valueHolder.increment();
        }
    }
}

Starting 10 threads to update our counter:

public class Main {
    public static void main(String[] args) {
        List<Thread> threads = new ArrayList<>();
        ValueHolder valueHolder = new ValueHolder();

        for(int i = 0 ; i < 10 ; i++ ) {
            Thread thread = new Thread(new UpdaterThread(valueHolder));
            thread.start();
            threads.add(thread);
        }
    }

}

In this case, both mutex and this works fine as a lock. But here is where the problem lies:

public class EvilThread implements Runnable {

    private final ValueHolder valueHolder;

    public EvilThread(ValueHolder valueHolder) {
        this.valueHolder = valueHolder;
    }


    @Override
    public void run() {
        try {
            synchronized (valueHolder) {
                Thread.sleep(20000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Start an evil thread and then threads that increments the counter:

public class Main {
    public static void main(String[] args) {
        List<Thread> threads = new ArrayList<>();
        ValueHolder valueHolder = new ValueHolder();
        
        Thread evilThread = new Thread(new EvilThread(valueHolder));
        evilThread.start();

        for(int i = 0 ; i < 10 ; i++ ) {
            Thread thread = new Thread(new UpdaterThread(valueHolder));
            thread.start();
            threads.add(thread);
        }
    }

}

EvilThread holds valueHolder lock. Now, if inside UpdaterThread we use this as a lock then we use the same lock as the EvilThread and we will have to wait. If we use mutex, then we don’t have to wait for EvilThread since the lock is not the same.

Conclusion

We have more control by using private mutex. Also, we can see all the places where the lock is used inside the class. If we use this as a lock, we would need to search the whole project to see where that lock is used.

Further reading

If this article was interesting to you, here are some great resources on the multithreading topic: