Java Synchronized Method vs Block: Lock Scope
java synchronized method vs block: Understand the differences between synchronized methods and blocks in Java, including lock scope, granularity, and performance impli...
In Java, the synchronized keyword can be applied either to a method or to a specific block of code. The choice between java synchronized method vs block determines which lock is acquired and how long it is held, which directly affects contention and throughput. This article explains the technical differences, the locking behavior, and the criteria you should use when deciding between the two approaches.
The Core Difference Between a Synchronized Method and a Synchronized Block
A synchronized method is declared by adding the synchronized modifier to the method signature. When a thread invokes a synchronized instance method, it acquires the monitor associated with the object on which the method is called. For static synchronized methods, the lock is the Class object representing the class. The lock is held for the entire duration of the method, and it is automatically released when the method returns or throws an exception.
A synchronized block, on the other hand, is an explicit block of code wrapped in synchronized (lockObject) { ... }. You can specify any object as the lock, not necessarily this. The lock is acquired when the thread enters the block and released when the block completes, even if an exception is thrown. This allows you to limit the critical section to only the statements that actually need mutual exclusion.
public class Counter { private int count = 0; // Synchronized method: locks on 'this' for the whole method public synchronized void incrementMethod() { count++; } // Synchronized block: locks on 'this' only for the increment statement public void incrementBlock() { // some non-critical code synchronized (this) { count++; } } }
In this example, both approaches protect the count field, but the synchronized block can be placed around only the critical operation, leaving other parts of the method outside the lock.
What Lock Is Acquired for Each Approach
For a synchronized instance method, the lock is the instance itself (this). For a static synchronized method, the lock is the Class object. This implicit locking is convenient but can be problematic if you need to coordinate with other code that synchronizes on the same object.
A synchronized block gives you explicit control over the lock object. You can use a dedicated lock object that is private to your class, preventing external code from interfering with your locking protocol. This is especially useful when you want to protect a shared resource without exposing the lock to callers.
public class Server { private final Object requestLock = new Object(); private List<String> requests = new ArrayList<>(); public void addRequest(String request) { synchronized (requestLock) { requests.add(request); } } }
Using a private lock object ensures that no other code can accidentally synchronize on the same monitor and cause unexpected contention or deadlocks.
Locking Granularity and Contention
The most significant practical difference between a synchronized method and a synchronized block is granularity. A synchronized method holds the lock for the entire method body, even if only a few lines actually modify shared state. This can lead to unnecessary contention, especially if the method performs long-running operations like I/O or complex calculations that do not need mutual exclusion.
A synchronized block allows you to shrink the critical section to the minimum required. This reduces the time a thread holds the lock, which in turn reduces the chance that other threads will block waiting for it. In high-concurrency scenarios, this can improve throughput significantly, though the exact benefit depends on the workload.
Consider a method that reads a configuration value from a slow source and then updates a shared cache. Synchronizing the entire method would hold the lock during the slow read, blocking other threads that only need to read the cache. A synchronized block around just the cache update avoids that.
public class Cache { private Map<String, String> cache = new HashMap<>(); public String getOrLoad(String key) { String value = cache.get(key); if (value == null) { // Simulate slow load value = loadFromDatabase(key); synchronized (this) { cache.put(key, value); } } return value; } }
Here, the lock is held only during the put operation, not during the database load.
Reentrancy and Nested Locks
Both synchronized methods and synchronized blocks are reentrant. A thread that already holds a monitor can reacquire the same monitor multiple times without deadlocking. This is essential for recursive methods and for calling other synchronized methods on the same object.
However, synchronized blocks give you more control when you need to acquire multiple locks. With methods, the lock is implicitly tied to the object, which can make lock ordering harder to manage. With blocks, you can acquire locks in a specific order and release them in a controlled manner, reducing the risk of deadlock.
For example, if you need to transfer money between two accounts, you must lock both accounts in a consistent order to avoid deadlock. Using synchronized blocks allows you to specify the order explicitly:
public void transfer(Account from, Account to, int amount) { Account first = from.hashCode() < to.hashCode() ? from : to; Account second = from.hashCode() < to.hashCode() ? to : from; synchronized (first) { synchronized (second) { from.debit(amount); to.credit(amount); } } }
This pattern is difficult to achieve with synchronized methods because the lock is always the receiver object, and you cannot easily enforce an ordering across two different objects.
Performance and Overhead Considerations
From a pure performance perspective, neither approach has a significant intrinsic overhead difference. The cost of acquiring and releasing a monitor is the same whether you use a method or a block. The real performance difference comes from contention: how long the lock is held and how often threads must wait.
A synchronized method can cause higher contention if the method contains code that does not need protection. This can lead to reduced parallelism and longer response times. A synchronized block, by limiting the critical section, can reduce the average waiting time for other threads. However, if the critical section is already very short, the difference may be negligible.
It is also worth noting that the JVM can apply optimizations like lock elision and biased locking, but these are implementation details and should not be the primary reason for choosing one approach. The decision should be based on correctness and the desired lock scope, not on micro-optimizations.
Choosing Between Method and Block Based on Your Use Case
Use a synchronized method when:
- The entire method body is a critical section that must be protected.
- The lock on
thisor theClassobject is acceptable and does not conflict with other synchronization. - The method is short and does not perform blocking I/O or long computations.
- You want the simplest syntax and do not need to expose a custom lock.
Use a synchronized block when:
- Only part of the method needs mutual exclusion.
- You need to lock on an object other than
this, such as a private lock or a shared resource. - You must acquire multiple locks in a specific order to avoid deadlock.
- You want to reduce contention by holding the lock for a shorter duration.
- You are working with a class that already uses
synchronizedmethods, and you need to avoid mixing lock scopes.
A common mistake is to synchronize a large method when only a few lines modify shared state. This can degrade concurrency unnecessarily. Conversely, using a synchronized block when the entire method is a critical section adds no benefit and can make the code harder to read.
Common Pitfalls and How to Avoid Them
One pitfall is synchronizing on a mutable object. If the lock object is reassigned or changed, threads may end up locking on different monitors, breaking mutual exclusion. Always use a final lock object, either a dedicated instance or the class itself.
Another issue is mixing synchronized methods and blocks on the same object. If one method uses synchronized (which locks on this) and another uses synchronized (this), they are equivalent. But if a block uses a different lock object, it will not coordinate with the method's lock, leading to race conditions. Ensure all code that accesses the same shared state uses the same lock.
Finally, be careful with synchronized blocks inside loops. Acquiring and releasing a lock repeatedly in a tight loop can cause overhead and contention. If the entire loop body needs protection, consider using a synchronized method or moving the loop inside the block, but be aware that holding the lock for a long time can block other threads.
A more subtle issue is that synchronized methods lock on this, which is publicly accessible. External code can also synchronize on the same object, potentially causing unexpected contention or deadlock. Using a private lock object inside a synchronized block avoids this exposure and gives you full control over the locking protocol.