Explain Codes LogoExplain Codes Logo

Java synchronized method lock on object, or method?

java
synchronized-methods
thread-safety
atomic-variables
Nikita BarsukovbyNikita Barsukov·Nov 1, 2024
TLDR

When it comes to synchronized instance methods, they lock on the current object (this). This essentially means no two threads can execute synchronized instance methods of the same object in parallel.

Example:

public synchronized void syncInstanceMethod() { // Imagine this as a VIP club, only one thread per instance can enter here }

Meanwhile, synchronized static methods utilise the class object (ClassName.class) as lock. So, no two threads can execute synchronized static methods of the same class simultaneously.

Example:

public static synchronized void syncStaticMethod() { // This is like an exclusive concert, only one thread for all instances can enter here }

Simplified breakdown of the components involved

The dexterity of synchronized blocks

Synchronized methods lock at either object or class level. At times, you might want to lock specific portions of an object, hence synchronized blocks come to the rescue. You can use them to lock an individual resource within a method, thereby efficiently reducing the performance impact and avoid unnecessary wait time for threads.

public void methodWithSynchronizedBlock(Object lock) { // Chilling out... synchronized(lock) { // Critical code jam session, only one thread with the same lock can party here } // Time to chill again... }

The simplicity of atomic variables

When it comes down to counter variables, or similar tasks, opting for AtomicInteger or other analogous atomic classes can help skip the need for synchronization. These atomic variables offer thread-safe operations on single variables by utilising mechanisms like compare-and-set, which can help avoid the overhead associated with synchronization.

private AtomicInteger atomicCount = new AtomicInteger(0); public void increment() { atomicCount.incrementAndGet(); // Just like cookies, the value automatically increments atomically }

Choosing the appropriate tool

While deliberating synchronization, it's key to select the technique that matches your needs ideally. Keep in mind factors like complexity and size of the critical section, the anticipated concurrency level, and whether your need relates to immutable operations or complex inter-thread interactions.

Choosing between synchronized and atomic variables

Achieving thread safety with less overhead

When performing simple atomic operations, consider using atomic classes. They prove especially useful working with primitive numbers demonstrating better performance due to reduced locking overhead.

Maintaining consistency with synchronized

If your goal is maintaining consistency of values across variables or coordinating complex interactions between threads, then synchronized methods will serve you better. Upon exiting a synchronized method, a happens-before relationship is established, ensuring memory visibility and thus subsequent reads reflect the latest updates.

Avoid performance pitfalls using synchronized

Avoid extensive use of synchronized as it can lead to suboptimal concurrency affecting performance. Remember, you're not just locking the method but potentially locking the object, leading to contention points and potentially slowing down your application.

Understanding the scope of locks and memory visibility

When to synchronize at instance level and when at class level

Recognising when to use synchronization at the instance level versus the class level is crucial. A static synchronized method locks on the class object, affecting all instances of that class, while an instance synchronized method locks on the specific instance.

Memory consistency impacts

Synchronization guarantees a happens-before relationship. Any thread entering a synchronized method or block sees all the modifications guarded by the same lock made prior to its entry.

Fine-grained locking practices

In some situations, using separate lock objects instead of this or ClassName.class, can provide a finer control on our program reducing contention among threads and hence improving scalability.