在我的上一個博客中,我研究了使用Java的傳統synchronized
關鍵字和鎖排序來修復破碎的,死鎖的余額轉移示例代碼。 但是,有一種替代方法稱為顯式鎖定。
這里,將鎖定機制稱為顯式而非隱式的想法是, 顯式表示它不是Java語言的一部分,并且已編寫了一些類來實現鎖定功能。 另一方面, 隱式鎖定可以定義為該語言的一部分,并且可以使用語言關鍵字synchronchized
在后臺實現鎖定。
您可以爭論明確鎖定是否是一個好主意。 是否應該對Java語言進行改進,使其包括顯式鎖定功能,而不是向已經龐大的API中添加另一組類? 例如: trysynchronized()
。
顯式鎖定基于Lock
接口及其ReentrantLock
實現 。 與傳統的synchronized
關鍵字相比, Lock
包含許多方法,這些方法使您對鎖定有更多控制。 它具有您期望的方法,例如lock()
會在代碼的受保護部分中創建入口點,而unlock()
會在代碼中創建出口點。 它還具有tryLock()
和tryLock(long time,TimeUnit unit)
僅當它可用且尚未被另一個線程獲取時才獲取tryLock()
,而tryLock(long time,TimeUnit unit)
將嘗試獲取一個鎖,如果不可用則等待指定的計時器。在放棄之前過期。
為了實現顯式鎖定,我首先向本系列以前的博客中使用的Account
類添加了Lock
接口。
public class Account implements Lock {private final int number;private int balance;private final ReentrantLock lock;public Account(int number, int openingBalance) {this.number = number;this.balance = openingBalance;this.lock = new ReentrantLock();}public void withDrawAmount(int amount) throws OverdrawnException {if (amount > balance) {throw new OverdrawnException();}balance -= amount;}public void deposit(int amount) {balance += amount;}public int getNumber() {return number;}public int getBalance() {return balance;}// ------- Lock interface implementation@Overridepublic void lock() {lock.lock();}@Overridepublic void lockInterruptibly() throws InterruptedException {lock.lockInterruptibly();}@Overridepublic Condition newCondition() {return lock.newCondition();}@Overridepublic boolean tryLock() {return lock.tryLock();}@Overridepublic boolean tryLock(long arg0, TimeUnit arg1) throws InterruptedException {return lock.tryLock(arg0, arg1);}@Overridepublic void unlock() {if (lock.isHeldByCurrentThread()) {lock.unlock();}}}
在上面的代碼中,您可以看到我贊成通過封裝一個ReentrantLock
對象來支持聚合, Account
類將鎖定功能委托給該對象。 唯一需要注意的小型GOTCHA是在unlock()
實現中:
@Overridepublic void unlock() {if (lock.isHeldByCurrentThread()) {lock.unlock();}}
它具有附加的if()
語句,該語句檢查調用線程是否是當前持有鎖的線程。 如果錯過了這一行代碼,那么您將獲得以下IllegalMonitorStateException
:
Exception in thread 'Thread-7' java.lang.IllegalMonitorStateExceptionat java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:460)at threads.lock.Account.unlock(Account.java:76)at threads.lock.TrylockDemo$BadTransferOperation.transfer(TrylockDemo.java:98)at threads.lock.TrylockDemo$BadTransferOperation.run(TrylockDemo.java:67)
那么,這是如何實現的呢? 下面是基于我的原始DeadLockDemo
程序的TryLockDemo
示例的列表。
public class TrylockDemo {private static final int NUM_ACCOUNTS = 10;private static final int NUM_THREADS = 20;private static final int NUM_ITERATIONS = 100000;private static final int LOCK_ATTEMPTS = 10000;static final Random rnd = new Random();List<Account> accounts = new ArrayList<Account>();public static void main(String args[]) {TrylockDemo demo = new TrylockDemo();demo.setUp();demo.run();}void setUp() {for (int i = 0; i < NUM_ACCOUNTS; i++) {Account account = new Account(i, 1000);accounts.add(account);}}void run() {for (int i = 0; i < NUM_THREADS; i++) {new BadTransferOperation(i).start();}}class BadTransferOperation extends Thread {int threadNum;BadTransferOperation(int threadNum) {this.threadNum = threadNum;}@Overridepublic void run() {int transactionCount = 0;for (int i = 0; i < NUM_ITERATIONS; i++) {Account toAccount = accounts.get(rnd.nextInt(NUM_ACCOUNTS));Account fromAccount = accounts.get(rnd.nextInt(NUM_ACCOUNTS));int amount = rnd.nextInt(1000);if (!toAccount.equals(fromAccount)) {boolean successfulTransfer = false;try {successfulTransfer = transfer(fromAccount, toAccount, amount);} catch (OverdrawnException e) {successfulTransfer = true;}if (successfulTransfer) {transactionCount++;}}}System.out.println("Thread Complete: " + threadNum + " Successfully made " + transactionCount + " out of "+ NUM_ITERATIONS);}private boolean transfer(Account fromAccount, Account toAccount, int transferAmount) throws OverdrawnException {boolean success = false;for (int i = 0; i < LOCK_ATTEMPTS; i++) {try {if (fromAccount.tryLock()) {try {if (toAccount.tryLock()) {success = true;fromAccount.withDrawAmount(transferAmount);toAccount.deposit(transferAmount);break;}} finally {toAccount.unlock();}}} finally {fromAccount.unlock();}}return success;}}
}
想法是一樣的,我有一個銀行帳戶列表,我將隨機選擇兩個帳戶,并從一個帳戶中隨機轉移一個金額。 問題的核心是我更新的transfer(...)
方法,如下所示。
private boolean transfer(Account fromAccount, Account toAccount, int transferAmount) throws OverdrawnException {boolean success = false;for (int i = 0; i < LOCK_ATTEMPTS; i++) {try {if (fromAccount.tryLock()) {try {if (toAccount.tryLock()) {success = true;fromAccount.withDrawAmount(transferAmount);toAccount.deposit(transferAmount);break;}} finally {toAccount.unlock();}}} finally {fromAccount.unlock();}}return success;}
這里的想法是我嘗試鎖定fromAccount
然后鎖定toAccount
。 如果可行,那么我先進行轉移,然后再記得解鎖兩個帳戶。 如果那時帳戶已經被鎖定,那么我的tryLock()
方法將失敗,整個過程將循環并再次嘗試。 嘗試10000次鎖定后,線程將放棄并忽略傳輸。 我猜想在現實世界的應用程序中,您希望將此故障放入某種隊列中,以便以后進行調查。
在使用顯式鎖定時,您必須考慮它的工作原理,因此請看下面的結果…
Thread Complete: 17 Successfully made 58142 out of 100000
Thread Complete: 12 Successfully made 57627 out of 100000
Thread Complete: 9 Successfully made 57901 out of 100000
Thread Complete: 16 Successfully made 56754 out of 100000
Thread Complete: 3 Successfully made 56914 out of 100000
Thread Complete: 14 Successfully made 57048 out of 100000
Thread Complete: 8 Successfully made 56817 out of 100000
Thread Complete: 4 Successfully made 57134 out of 100000
Thread Complete: 15 Successfully made 56636 out of 100000
Thread Complete: 19 Successfully made 56399 out of 100000
Thread Complete: 2 Successfully made 56603 out of 100000
Thread Complete: 13 Successfully made 56889 out of 100000
Thread Complete: 0 Successfully made 56904 out of 100000
Thread Complete: 5 Successfully made 57119 out of 100000
Thread Complete: 7 Successfully made 56776 out of 100000
Thread Complete: 6 Successfully made 57076 out of 100000
Thread Complete: 10 Successfully made 56871 out of 100000
Thread Complete: 11 Successfully made 56863 out of 100000
Thread Complete: 18 Successfully made 56916 out of 100000
Thread Complete: 1 Successfully made 57304 out of 100000
這些表明,盡管該程序沒有死鎖并無限期地掛起,但它僅設法使余額轉移只超過轉移請求的一半。 這意味著它正在消耗大量的處理能力,包括循環,循環和循環-總體上不是很有效。 另外,我剛才說過該程序“沒有死鎖并無限期地掛起”,這不是真的。 如果您考慮發生了什么,那么您將意識到程序陷入僵局,然后退出這種情況。
我的顯式鎖定演示代碼的第二個版本使用上面提到的tryLock(long time,TimeUnit unit)
。
private boolean transfer(Account fromAccount, Account toAccount, int transferAmount) throws OverdrawnException {boolean success = false;try {if (fromAccount.tryLock(LOCK_TIMEOUT, TimeUnit.MILLISECONDS)) {try {if (toAccount.tryLock(LOCK_TIMEOUT, TimeUnit.MILLISECONDS)) {success = true;fromAccount.withDrawAmount(transferAmount);toAccount.deposit(transferAmount);}} finally {toAccount.unlock();}}} catch (InterruptedException e) {e.printStackTrace();} finally {fromAccount.unlock();}return success;}
在這段代碼中,我用1毫秒的tryLock(...)
超時替換了for
循環。 這意味著,當tryLock(...)
被調用并且無法獲取鎖定時,它將等待1 ms,然后回滾并放棄。
Thread Complete: 0 Successfully made 26637 out of 100000
Thread Complete: 14 Successfully made 26516 out of 100000
Thread Complete: 3 Successfully made 26552 out of 100000
Thread Complete: 11 Successfully made 26653 out of 100000
Thread Complete: 7 Successfully made 26399 out of 100000
Thread Complete: 1 Successfully made 26602 out of 100000
Thread Complete: 18 Successfully made 26606 out of 100000
Thread Complete: 17 Successfully made 26358 out of 100000
Thread Complete: 19 Successfully made 26407 out of 100000
Thread Complete: 16 Successfully made 26312 out of 100000
Thread Complete: 15 Successfully made 26449 out of 100000
Thread Complete: 5 Successfully made 26388 out of 100000
Thread Complete: 8 Successfully made 26613 out of 100000
Thread Complete: 2 Successfully made 26504 out of 100000
Thread Complete: 6 Successfully made 26420 out of 100000
Thread Complete: 4 Successfully made 26452 out of 100000
Thread Complete: 9 Successfully made 26287 out of 100000
Thread Complete: 12 Successfully made 26507 out of 100000
Thread Complete: 10 Successfully made 26660 out of 100000
Thread Complete: 13 Successfully made 26523 out of 100000
上面的結果表明,使用計時器時,余額轉移成功率甚至下降到25%以上。 盡管現在還沒有消耗大量的處理器時間,但效率仍然很低。
在相當長的一段時間內,我可能會花時間處理這兩個代碼示例,從而選擇可以優化應用程序并提高性能的變量,但最終,沒有什么真正的選擇可以正確地進行鎖排序。 我個人更愿意在可能的情況下使用老式的synchronized
關鍵字隱式鎖定,并為死鎖代碼過時,陳舊,難以理解的少數情況保留顯式鎖定,我已經嘗試了其他所有方法,該應用需要上線,已經很晚了,該回家了……
有關更多信息,請參閱本系列中的其他博客 。
該系列以及其他博客的所有源代碼都可以在Github上找到,網址為git://github.com/roghughe/captaindebug.git
參考: 調查死鎖–第5部分:使用來自Captain Debug博客博客的JCG合作伙伴 Roger Hughes的顯式鎖定 。
翻譯自: https://www.javacodegeeks.com/2012/11/investigating-deadlocks-part-5-using-explicit-locking.html