生活随笔
收集整理的這篇文章主要介紹了
银行转账
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
銀行轉賬是一道典型的考察多線程的題目,該題目主要考察對鎖的對象的選擇以及對死鎖的了解,如何避免死鎖。
重要!重要!重要!文中所有方法,經測試均未實現安全的轉賬功能(包括設置同步方法),本文只作為自己的學習記錄,所以初學者慎重考慮,以免被帶入歧途!!!
另外,期望有大佬能指出不能安全轉賬的問題所在!不勝感激!!!
當然,初學者也可以發表自己的意見,大家一起討論。
為方便閱讀,以下所有代碼可能會刪除一部分業務邏輯
完整代碼
如果我們簡單的將轉賬方法設置為同步方法。肯定可以實現正確的同步。但這樣有嚴重的性能問題,當用戶A向用戶B轉賬的時候,用戶C想要轉賬給用戶D,必須等待用戶A轉賬完畢才行。本來兩者沒有任何的數據依賴關系,強行實現同步,導致每次只能有一個用戶進行轉賬操作。這肯定不是我們想要的。
代碼:
public synchronized void transfer(String from, String to, int money){//轉賬邏輯}
實際上我們在轉賬的時候只是不允許別人對轉賬的這兩個賬戶進行操作,否則就有可能出現數據的錯誤。那我們是不是可以對賬戶上鎖,只要保證其他用戶無法對轉賬的兩個賬戶操作即可。
代碼如下:
public synchronized boolean transferAccounts(String accountA, String transferAccount, int money) {UserAccount account = BankLock.getUserAccount(accountA);boolean success = false;UserAccount you = BankLock.getUserAccount(transferAccount);if(account.getId() > you.getId()){synchronized (you) {synchronized (account) {success = transferUtil.transfer(account, you, money);}}}else{synchronized (account) {synchronized (you) {success = transferUtil.transfer(account, you, money);}}}return success;}
當我們對賬戶上鎖的時候,就會引出來另一個問題,死鎖。注意到上述的代碼if和else中的代碼邏輯基本相同,只是加鎖順序不同。該邏輯即為了避免死鎖問題。
避免死鎖的方法:
控制鎖的順序,我們以一種固定的順序去加鎖,比如上述的代碼,比較兩個賬戶id,每次都是先鎖id值較小的賬戶。限時獲取鎖,使用Lock鎖,當一定的時間內無法獲取到想要的鎖。則釋放已經獲取到的鎖,等待一段時間后再重新獲取。
方法2代碼如下:
public boolean transferAccounts(String accountA, String transferAccount, int money) {UserAccount account = accountDao.getAccount(accountA);Lock lockA = BankLock.getLock(account);int times = 0;boolean success = false;UserAccount you = accountDao.getAccount(transferAccount);lockA.lock();boolean tryLockB = false;try {Lock lockB = BankLock.getLock(you);//嘗試三次,若還未獲取成功,則轉賬失敗退出。while(times++ < 3){try {tryLockB = lockB.tryLock(1, TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();}if(!tryLockB){Condition condition = BankLock.getLockCondition(account);//1s內沒有獲取到鎖B,先釋放鎖A等待1秒,然后重新執行try {condition.await(1,TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();}}else{break;}}if(tryLockB){try {success = transferUtil.transfer(account, you, money);} finally {lockB.unlock();}}} finally {Condition condition = BankLock.getLockCondition(account);condition.signalAll();lockA.unlock();}return success;}
下邊給出一段測試代碼:
public void testTransfer1(){for(int i = 0; i < 10; i++){new Thread(new Runnable() {@Overridepublic void run() {while(true){Random random = new Random();//最多操作三個賬戶int accountA = (random.nextInt(1000) % 3);int accountB = (random.nextInt(1000) % 3);if (accountA == accountB)continue;int money = random.nextInt(1000);System.out.println(accountA + " to " + accountB + " : " + money + " " + Thread.currentThread().getName());amountService.transferAccounts(String.valueOf(accountA), String.valueOf(accountB), money);try {Thread.sleep(2000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}},"thread-" + i).start();}try {Thread.sleep(1000000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
附1:一段錯誤的代碼示范,以下代碼可能引起死鎖。該段代碼主要引起死鎖的原因是對await方法不熟悉導致的,知其然不知其所以然。調用await方法會釋放鎖等待,當有其他線程調用signal或signalAll方法時,會將該線程重新放到阻塞隊列獲取鎖,不需要顯示獲取鎖,所以最后該線程仍然需要釋放鎖。
代碼如下:
public boolean transferAccounts(String accountA, String transferAccount, int money) {UserAccount account = accountDao.getAccount(accountA);Lock lockA = BankLock.getLock(account);int times = 0;boolean success = false;UserAccount you = accountDao.getAccount(transferAccount);while(times++ < 3){lockA.lock();boolean tryLockB = false;try {Lock lockB = BankLock.getLock(you);try {tryLockB = lockB.tryLock(1, TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();}if(tryLockB){try {//轉賬break;} finally {lockB.unlock();}}} finally {if(tryLockB){Condition condition = BankLock.getLockCondition(account);condition.signalAll();lockA.unlock();}else{Condition condition = BankLock.getLockCondition(account);//1s內沒有獲取到鎖B,先釋放鎖A等待1秒,然后重新執行try {condition.await(1,TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();}}}}return success;}
總結
以上是生活随笔為你收集整理的银行转账的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。