Why Simple Locks Fail When Protecting Multiple Related Resources
This article explains how using a lock that protects only a single resource can lead to incorrect behavior when multiple related resources are involved, demonstrates the problem with Java synchronized methods, and shows how a class‑level lock resolves the issue.
Introduction
The previous article introduced the atomicity problem and the lock‑resource model. Building on that foundation, this piece examines how protecting a single resource works and why the same approach breaks down for multiple related resources.
Protecting a Single Resource
Create a lock for the protected resource R.
Acquire the lock to enter the critical section.
Release the lock to exit the critical section.
The key is that the direction of the lock ("R1's lock protects R1") must be correct.
Protecting Multiple Unrelated Resources
If resources are unrelated, each can be protected with its own lock, as illustrated by the example of a bank withdrawal (protecting balance) and a password change (protecting password). Separate locks work fine because the resources do not interact.
For example, a bank withdrawal and a password change operate on completely independent resources.
Protecting Multiple Related Resources
Consider a classic bank transfer: account A transfers 100 units to account B. The two balances are related, so protecting each with its own lock can cause inconsistencies.
class Account {
private int balance;
// transfer
synchronized void transfer(Account target, int amt) {
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}Using synchronized on the instance method locks only this, not the target account, so the lock does not protect the related resource.
Assume accounts A, B, C each start with a balance of 200. A transfers 100 to B while B simultaneously transfers 100 to C.
Thread 1 locks A.this and thread 2 locks B.this. Neither thread locks the other’s target, so both can enter the critical section concurrently, violating the monitor‑lock rule and the happens‑before transitivity rule. This leads to nondeterministic final balances for account B (either 100 or 300).
Correct Approach
To protect all related resources, increase the lock granularity by using a class‑level lock ( Account.class), which is unique for all instances.
class Account {
private int balance;
// transfer
void transfer(Account target, int amt) {
synchronized(Account.class) {
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
}The Account.class lock is shared by all Account objects, ensuring that any transfer involving any accounts is serialized.
Conclusion
Understanding the relationship between locks and resources is essential. Single resources or multiple unrelated resources can be protected with individual locks. For multiple related resources, a larger‑granularity lock (e.g., a class‑level lock) is required, though it may affect performance. Future articles will explore finer‑grained locking strategies.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
