ReentrantLock - параллельная операция по переводу денег

Когда я читал в Интернете несколько примеров кода параллелизма, я нашел этот (операция перевода денег между двумя банковскими счетами):

class Account {
     double balance;
     int id;
     public Account(int id, double balance){
          this.balance = balance;
          this.id = id;
     }
     void withdraw(double amount){
          balance -= amount;
     } 
     void deposit(double amount){
          balance += amount;
     }
}
class Main{
     public static void main(String [] args){
           final Account a = new Account(1,1000);
           final Account b = new Account(2,300);
           Thread a = new Thread(){
                 public void run(){
                     transfer(a,b,200);
                 }
           };
           Thread b = new Thread(){
                 public void run(){
                     transfer(b,a,300);
                 }
           };
           a.start();
           b.start();
     }

И этот фрагмент кода, который решает проблему параллелизма с использованием ReentrantLock:

private final Lock lock = new ReentrantLock(); //Addition to the Account class

public static void transfer(Account from, Account to, double amount)
{
       while(true)
        {
          if(from.lock.tryLock()){
            try { 
                if (to.lock.tryLock()){
                   try{
                       from.withdraw(amount);
                       to.deposit(amount);
                       break;
                   } 
                   finally {
                       to.lock.unlock();
                   }
                }
           }
           finally {
                from.lock.unlock();
           }

           Thread.sleep(someRandomTimeToPreventLiveLock);
        }
 }

Мой вопрос: не должны ли методы Acount снять () и deposit() каким-либо образом быть защищены (синхронизированы или заблокированы с полем ReentrantLock), чтобы этот пример работал? Разве не возможно, чтобы другой поток закрался и вызвал метод снятия или депозита? Кроме того, что если есть метод getBalance()? Должно ли оно быть защищено (синхронизировано или заблокировано с помощью ReentrantLock)?

2 ответа

Решение

Есть два варианта:

(1) Вы делаете ваш класс поточно-ориентированным, что означает, что любая операция в любом экземпляре этого класса защищена каким-то внутренним механизмом и абсолютно безопасна в многопоточной среде. Сторона вызывающего абонента не должна заботиться о безопасности потока.

Вот что я бы предпочел здесь. Как потребитель вашего API, я бы, кроме как Account#withdraw а также Account#deposit быть самодостаточным, поэтому никаких дополнительных действий не потребуется.

Вот как выглядит хороший API.

(2) Вы возлагаете ответственность за обеспечение правильности и безопасности потоков на сторону вызывающего абонента. Вам все равно, как это будет достигнуто.

Вот как ваши фрагменты в настоящее время работают. Метод transfer Потокобезопасен, но он не делает операции с учетными записями такими.

не следует ли каким-либо образом защищать методы отзыва () и депозита () Аккаунта

На самом деле, когда выполняется нижняя строка, блок кода охраняется Lock объект (и каждый объект учетной записи имеет свой собственный Lock объект). Таким образом, ни один другой поток не может выполнить тот же код, используя тот же Account пример.

while(true)
    {
      if(from.lock.tryLock()){
        try { 
            if (to.lock.tryLock()){
               try{
          ....
          ....

С другой стороны, когда вы выполняете код, вы создаете несколько Account объект, который делает каждую передачу независимой друг от друга. Как каждый Account объект имеет свое собственное состояние (balance, lock)

Кроме того, что если есть метод getBalance()? Должен ли он быть защищен тоже

Как указано выше.

Другие вопросы по тегам