...
Additionally, synchronization should be limited to cases where it is absolutely necessary. For example, the paint()
, dispose()
, stop()
, and destroy()
methods should never be synchronized in an applet because they are always called and used from dedicated threads. The Thread.stop()
and Thread.destroy()
methods are deprecated. For more information, see See rule THI05-J. Do not use Thread.stop() to terminate threads for more information.
This rule also applies to programs that need to work with a limited set of resources. For example, liveness issues can arise when two or more threads are waiting for each other to release resources such as database connections. These issues can be resolved by letting each waiting thread retry the operation at random intervals , until they succeed in acquiring the resource successfully.
...
Code Block | ||
---|---|---|
| ||
final class BankAccount { private double balanceAmount; // Total amount in bank account BankAccount(double balance) { this.balanceAmount = balance; } // Deposits the amount from this object instance // to BankAccount instance argument ba private void depositAmount(BankAccount ba, double amount) { synchronized (this) { synchronized (ba) { if(amount > balanceAmount) { throw new IllegalArgumentException( "Transfer cannot be completed" ); } ba.balanceAmount += amount; this.balanceAmount -= amount; } } } public static void initiateTransfer(final BankAccount first, final BankAccount second, final double amount) { Thread transfer = new Thread(new Runnable() { public void run() { first.depositAmount(second, amount); } }); transfer.start(); } } |
...
This noncompliant code example may or may not deadlock, depending on the scheduling details of the platform. Deadlock will occur occurs when (a1) two threads request the same two locks in different orders, and (b2) each thread obtains a lock that prevents the other thread from completing its transfer. Deadlock will be is avoided when two threads request the same two locks but one thread completes its transfer before the other thread begins. Similarly, deadlock will be is avoided if the two threads request the same two locks in the same order (which would happen if they both transfer money from one account to a second account) or if two transfers involving distinct accounts occur concurrently.
...
This compliant solution avoids deadlock by synchronizing on a private static final lock object before performing any account transfers.
Code Block | ||
---|---|---|
| ||
final class BankAccount { private double balanceAmount; // Total amount in bank account private static final Object lock = new Object(); BankAccount(double balance) { this.balanceAmount = balance; } // Deposits the amount from this object instance // to BankAccount instance argument ba private void depositAmount(BankAccount ba, double amount) { synchronized (lock) { if (amount > balanceAmount) { throw new IllegalArgumentException("Transfer cannot be completed"); } "Transfer cannot be completed"); } ba.balanceAmount += amount; this.balanceAmount -= amount; } } public static void initiateTransfer(final BankAccount first, final BankAccount second, final double amount) { Thread transfer = new Thread(new Runnable() { @Override public void run() { first.depositAmount(second, amount); } }); transfer.start(); } } |
In this scenario, deadlock cannot occur when two threads with two different BankAccount
objects try to transfer to each othersother's accounts simultaneously. One thread will acquire the private lock, complete its transfer, and release the lock before the other thread can proceed.
This solution imposes a performance penalty because a private static lock restricts the system to performing transfers sequentially. Two transfers involving four distinct accounts (with distinct target accounts) cannot be performed concurrently. This penalty increases considerably as the number of BankAccount
objects increase. Consequently, this solution fails to scale well.
...
This compliant solution ensures that multiple locks are acquired and released in the same order. It requires that a consistent ordering over BankAccount
objects. Consequently, the BankAccount
class implements the java.lang.Comparable
interface and overrides the compareTo()
method.
Code Block | ||
---|---|---|
| ||
final class BankAccount implements Comparable<BankAccount> { private double balanceAmount; // Total amount in bank account private final Object lock; private final long id; // Unique for each BankAccount private static long NextID = 0; // Next unused ID BankAccount(double balance) { this.balanceAmount = balance; this.lock = new Object(); this.id = this.NextID++; } @Override public int compareTo(BankAccount ba) { return (this.id > ba.id) ? 1 : (this.id < ba.id) ? -1 : 0; } // Deposits the amount from this object instance // to BankAccount instance argument ba public void depositAmount(BankAccount ba, double amount) { BankAccount former, latter; if (compareTo(ba) < 0) { former = this; latter = ba; } else { former = ba; latter = this; } synchronized (former) { synchronized (latter) { if (amount > balanceAmount) { throw new IllegalArgumentException( "Transfer cannot be completed"); } ba.balanceAmount += amount; this.balanceAmount -= amount; } } } public static void initiateTransfer(final BankAccount first, final BankAccount second, final double amount) { Thread transfer = new Thread(new Runnable() { @Override public void run() { first.depositAmount(second, amount); } }); transfer.start(); } } |
Whenever a transfer occurs, the two BankAccount
objects are ordered so that the first
object's lock is acquired before the second
object's lock. Consequently, when When two threads attempt transfers between the same two accounts, they will each try to acquire the first account's lock before the acquiring the second account's lock. Consequently, one thread will acquire acquires both locks, complete completes the transfer, and release releases both locks before the other thread can proceed.
Unlike in the previous compliant solution, this solution permits multiple concurrent transfers , as long as the transfers involve distinct target accounts.
...
Code Block | ||
---|---|---|
| ||
final class BankAccount { private double balanceAmount; // Total amount in bank account private final Lock lock = new ReentrantLock(); private final Random number = new Random(123L); BankAccount(double balance) { this.balanceAmount = balance; } // Deposits amount from this object instance to // to BankAccount instance argument ba private void depositAmount(BankAccount ba, double amount) throws InterruptedException { while (true) { if (this.lock.tryLock()) { try { if (ba.lock.tryLock()) { try { if (amount > balanceAmount) { throw new IllegalArgumentException( "Transfer cannot be completed"); } ba.balanceAmount += amount; this.balanceAmount -= amount; break; } finally { ba.lock.unlock(); } } } finally { this.lock.unlock(); } } int n = number.nextInt(1000); int TIME = 1000 + n; // 1 second + random delay to prevent livelock Thread.sleep(TIME); } } public static void initiateTransfer(final BankAccount first, final BankAccount second, final double amount) { Thread transfer = new Thread(new Runnable() { public void run() { try { first.depositAmount(second, amount); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // Reset interrupted status } } }); transfer.start(); } } |
...
Code that uses this locking strategy has behavior similar to that of synchronized code that uses the traditional monitor lock. ReentrantLock
also provides several other capabilities. For example, the tryLock()
method never blocks waiting , when another thread already holds the lock. Further, the java.util.concurrent.locks.ReentrantReadWriteLock
class has multiple-reader/single-writer semantics , and is useful when some threads require a lock to write information , while other threads require the lock to concurrently read the information.
...
For example, when there are 20 requests in the vector, and one thread calls getAverageBandwidth()
, the thread acquires the intrinsic lock of WebRequest
0, the first element in the vector. Meanwhile, if a second thread calls getAverageResponseTime()
, it acquires the intrinsic lock of web request WebRequest
19, the last element in the vector. Consequently, deadlock results because neither thread can acquire all of the locks required to proceed with its calculations.
...
Code Block | ||
---|---|---|
| ||
public final class WebRequestAnalyzer { private final Vector<WebRequest> requests = new Vector<WebRequest>(); public boolean addWebRequest(WebRequest request) { return requests.add(new WebRequest(request.getBandwidth(), request.getResponseTime())); } public double getAverageBandwidth() { if (requests.size() == 0) { throw new IllegalStateException("The vector is empty!"); } return calculateAverageBandwidth(0, 0); } public double getAverageResponseTime() { if (requests.size() == 0) { throw new IllegalStateException("The vector is empty!"); } return calculateAverageResponseTime(0, 0); } private double calculateAverageBandwidth(int i, long bandwidth) { if (i == requests.size()) { return bandwidth / requests.size(); } synchronized (requests.elementAt(i)) { // Acquires locks in increasing order bandwidth += requests.get(i).getBandwidth(); return calculateAverageBandwidth(++i, bandwidth); } } private double calculateAverageResponseTime(int i, long responseTime) { if (i == requests.size()) { return responseTime / requests.size(); } synchronized (requests.elementAt(i)) { // Acquires locks in increasing order responseTime += requests.get(i).getResponseTime(); // Acquires locks in increasing order return calculateAverageResponseTime(++i, responseTime); } } } |
Consequently, while one thread is calculating the average bandwidth or response time, another thread cannot interfere or induce deadlock. Each thread thread must first synchronize on the first web request, which cannot happen until any prior calculation completes.
Locking on the last element of the vector in addWebRequest()
is unnecessary for two reasons: (1) locks are acquired in increasing order in all the methods, and (2) updates to the vector are reflected in the results of the computations.
...
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
LCK07-J | low | likely | high | P3 | L3 |
Automated Detection
SureLogic Flashlight Some static analysis tools can detect violations of this rule. It flags both the noncompliant code examples by specifying: "potential for deadlock."
Related Guidelines
CWE-833, ". Deadlock " |
Bibliography
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="a18795b7580e9b19-6a86abea-4d7842f2-b03e94be-956144d36b9c1375083670da"><ac:plain-text-body><![CDATA[ | [[JLS 2005 | AA. Bibliography#JLS 05]] | [Chapter 17, Threads and Locks | http://java.sun.com/docs/books/jls/third_edition/html/memory.html] | ]]></ac:plain-text-body></ac:structured-macro> |
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="3dbb8976540ad9eb-133ab208-49bb4efc-b70a85f6-013d202bd7981c1c14cdafad"><ac:plain-text-body><![CDATA[ | [[Halloway 2000 | AA. Bibliography#Halloway 00]] |
| ]]></ac:plain-text-body></ac:structured-macro> |
...