...
This noncompliant code example may or may not deadlock, depending on the scheduling details of the platform. Deadlock will occur when two threads request the same two locks in different orders and each thread obtains a lock that prevents the other thread from completing its transfer. Deadlock will not occur when two threads request the same two locks but one thread completes its transfer before the other thread begins. SimiliarlySimilarly, deadlock will not occur 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.
Compliant Solution (
...
Private Static Final Lock Object)
The deadlock can be avoided by synchronizing on a private static final
lock object before performing any account transfers.
...
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 idID 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(); } } |
...
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(requests.size() - 1, 0); } private double calculateAverageBandwidth(int i, long bandwidth) { if (i == requests.size()) { return bandwidth / requests.size(); } synchronized (requests.elementAt(i)) { bandwidth += requests.get(i).getBandwidth(); // Acquires locks in increasing order return calculateAverageBandwidth(++i, bandwidth); } } private double calculateAverageResponseTime(int i, long responseTime) { if (i <= -1) { return responseTime / requests.size(); } synchronized (requests.elementAt(i)) { responseTime += requests.get(i).getResponseTime(); // Acquires locks in decreasing order return calculateAverageResponseTime(--i, responseTime); } } } |
...
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)) { 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. That is because the other thread first needs to synchronize on the first WebRequest
web request, which cannot happen before the first calculation completes.
...