В последнем из этой небольшой серии блогов, в которых я говорил об анализе взаимоблокировок, я собираюсь исправить свой код BadTransferOperation .  Если вы видели другие блоги в этой серии , вы будете знать, что для этого я создал демо-код, который блокирует блокировку, показал, как получить дамп потока, а затем проанализировал дамп потока, выяснить, где и как возникла тупиковая ситуация.  В целях экономии места в приведенном ниже обсуждении используются классы Account и DeadlockDemo из первой части этой серии , в которой содержатся полные списки кодов. 
Описание тупиковых ситуаций в учебниках обычно выглядит примерно так: «Поток A получит блокировку на объекте 1 и будет ожидать блокировки на объекте 2, тогда как поток B получит блокировку на объекте 2, ожидая блокировки на объекте 1». Скопление, показанное в моем предыдущем блоге и выделенное ниже, является реальной тупиковой ситуацией, когда другие потоки, блокировки и объекты мешают прямой, простой, теоретической тупиковой ситуации.
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 | Found one Java-level deadlock:============================='Thread-21':  waiting to lock monitor 7f97118bd560 (object 7f3366f58, a threads.deadlock.Account),  which is held by 'Thread-20''Thread-20':  waiting to lock monitor 7f97118bc108 (object 7f3366e98, a threads.deadlock.Account),  which is held by 'Thread-4''Thread-4':  waiting to lock monitor 7f9711834360 (object 7f3366e80, a threads.deadlock.Account),  which is held by 'Thread-7''Thread-7':  waiting to lock monitor 7f97118b9708 (object 7f3366eb0, a threads.deadlock.Account),  which is held by 'Thread-11''Thread-11':  waiting to lock monitor 7f97118bd560 (object 7f3366f58, a threads.deadlock.Account),  which is held by 'Thread-20' | 
  Если вы свяжете текст и изображение выше со следующим кодом, вы увидите, что Thread-20 заблокировал свой объект fromAccount (f58) и ожидает блокировки своего объекта toAccount (e98) 
| 1 2 3 4 5 6 7 8 9 | privatevoidtransfer(Account fromAccount, Account toAccount, inttransferAmount) throwsOverdrawnException {      synchronized(fromAccount) {        synchronized(toAccount) {          fromAccount.withdraw(transferAmount);          toAccount.deposit(transferAmount);        }      }    } | 
  К сожалению, из-за проблем с синхронизацией, Thread-20 не может получить блокировку для объекта e98, потому что он ожидает, пока Thread-4 освободит свою блокировку для этого объекта.  Thread-4 не может снять блокировку, потому что он ожидает Thread-7 , Thread-7 ожидает Thread-11 а Thread-11 ожидает, пока Поток Thread-20 снимет свою блокировку на объекте f58.  Этот реальный тупик — просто более сложная версия описания учебника. 
  Проблема с этим кодом заключается в том, что из приведенного ниже фрагмента видно, что я случайно выбираю два объекта Account из массива Accounts в качестве fromAccount и toAccount и блокирую их.  Поскольку fromAccount и toAccount могут ссылаться на любой объект из массива toAccount , это означает, что они заблокированы в случайном порядке. 
| 1 2 | Account toAccount = accounts.get(rnd.nextInt(NUM_ACCOUNTS));Account fromAccount = accounts.get(rnd.nextInt(NUM_ACCOUNTS)); | 
  Следовательно, исправление состоит в том, чтобы навести порядок блокирования объекта Account и выполнения любого ордера, если он согласован. 
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 | privatevoidtransfer(Account fromAccount, Account toAccount, inttransferAmount) throwsOverdrawnException {     if(fromAccount.getNumber() > toAccount.getNumber()) {       synchronized(fromAccount) {         synchronized(toAccount) {           fromAccount.withdraw(transferAmount);           toAccount.deposit(transferAmount);         }       }     } else{       synchronized(toAccount) {         synchronized(fromAccount) {           fromAccount.withdraw(transferAmount);           toAccount.deposit(transferAmount);         }       }     }   } | 
  Код выше показывает исправление.  В этом коде я использую номер Account записи, чтобы сначала заблокировать объект Account с наибольшим номером учетной записи, чтобы вышеописанная ситуация взаимоблокировки никогда не возникала. 
Код ниже — это полный список исправлений:
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | publicclassAvoidsDeadlockDemo {  privatestaticfinalintNUM_ACCOUNTS = 10;  privatestaticfinalintNUM_THREADS = 20;  privatestaticfinalintNUM_ITERATIONS = 100000;  privatestaticfinalintMAX_COLUMNS = 60;  staticfinalRandom rnd = newRandom();  List<Account> accounts = newArrayList<Account>();  publicstaticvoidmain(String args[]) {    AvoidsDeadlockDemo demo = newAvoidsDeadlockDemo();    demo.setUp();    demo.run();  }  voidsetUp() {    for(inti = 0; i < NUM_ACCOUNTS; i++) {      Account account = newAccount(i, rnd.nextInt(1000));      accounts.add(account);    }  }  voidrun() {    for(inti = 0; i < NUM_THREADS; i++) {      newBadTransferOperation(i).start();    }  }  classBadTransferOperation extendsThread {    intthreadNum;    BadTransferOperation(intthreadNum) {      this.threadNum = threadNum;    }    @Override    publicvoidrun() {      for(inti = 0; i < NUM_ITERATIONS; i++) {        Account toAccount = accounts.get(rnd.nextInt(NUM_ACCOUNTS));        Account fromAccount = accounts.get(rnd.nextInt(NUM_ACCOUNTS));        intamount = rnd.nextInt(1000);        if(!toAccount.equals(fromAccount)) {          try{            transfer(fromAccount, toAccount, amount);            System.out.print(".");          } catch(OverdrawnException e) {            System.out.print("-");          }          printNewLine(i);        }      }      System.out.println("Thread Complete: "+ threadNum);    }    privatevoidprintNewLine(intcolumnNumber) {      if(columnNumber % MAX_COLUMNS == 0) {        System.out.print("\n");      }    }    /**     * This is the crucial point here. The idea is that to avoid deadlock you need to ensure that threads can't try     * to lock the same two accounts in the same order     */    privatevoidtransfer(Account fromAccount, Account toAccount, inttransferAmount) throwsOverdrawnException {      if(fromAccount.getNumber() > toAccount.getNumber()) {        synchronized(fromAccount) {          synchronized(toAccount) {            fromAccount.withdraw(transferAmount);            toAccount.deposit(transferAmount);          }        }      } else{        synchronized(toAccount) {          synchronized(fromAccount) {            fromAccount.withdraw(transferAmount);            toAccount.deposit(transferAmount);          }        }      }    }  }} | 
  В моем примере кода возникает тупик из-за проблемы синхронизации и вложенных synchronized ключевых слов в моем классе BadTransferOperation .  В этом коде synchronized ключевые слова находятся на соседних строках;  однако, в заключение, стоит отметить, что не имеет значения, где в вашем коде synchronized ключевые слова (они не обязательно должны быть смежными).  Пока вы блокируете два (или более) разных объекта монитора одним и тем же потоком, упорядочение имеет место и возникают взаимные блокировки. 
Для получения дополнительной информации см. Другие блоги в этой серии .
Весь исходный код для этого и других блогов из этой серии доступен на Github по адресу: gitub.com/roghughe/captaindebug.git.
Ссылка: Исследование тупиков — Часть 4. Исправление кода от нашего партнера по JCG Роджера Хьюза в блоге Captain Debug’s Blog .


