Два потока используют один и тот же метод, несмотря на использование lock () в Java коде? - PullRequest
0 голосов
/ 28 апреля 2020

У меня есть простой код в Java. Целью является проверка входной блокировки () Java. Есть класс с именем Bank (код ниже), который содержит массив объектов Account (код для класса Account ниже).

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

AccountThread расширяет поток, который будет запускать каждую учетную запись как поток. Он содержит ссылку на объект «Банк».

TestBank имеет метод (publi c stati c void main (String args []) и будет запускать объекты.

It инициализирует объект Bank, создаст 10 объектов AccountThread, передаст ссылку на этот объект Bank этим объектам AccountThread, затем он запустит все эти 10 потоков (с объектами AccountThread).

Эти 10 учетных записей переводят только деньги друг с другом. Каждый поток будет иметь свою собственную учетную запись.

Эти потоки будут бесконечно l oop, случайным образом выбирают учетную запись (одну из 9 других учетных записей) и переводят случайную сумму на эту учетную запись.

После каждых 10 000 транзакций программа выводит некоторые данные, одним из которых является «сумма баланса» (сумма баланса всех этих 10 счетов).

Эта сумма никогда не должна меняться , поскольку счета переводят деньги друг другу.

Я использовал lock.lock () и lock.unlock () в методах «депозит» и «снятие», так что пока есть пополнение счета или снятие средств с одного аккаунта, другие потоки не должны получать доступ к этим методам.

Но есть некоторые проблемы:

Но проблема в том, что я получаю некоторые отклонения от суммы Я не понимаю, с чем это связано.

Я пытался использовать "AccountThread", поскольку класс реализует Runnable, но результаты те же.

Вот что я пытался:

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

for (Account account: accounts) {account.getLock().lock();} перед вызовом test () и toString ()

и после test () и toString () я позвонил

for (Account account: accounts) {account.getLock().unlock();} 

Но результаты совпадают.

2) Я заблокировал только две учетные записи, которые только использовались при передаче перед запуском test (), чтобы другие потоки не использовали их для учетных записей. Но это тоже не работает.

Я пробовал и другие комбинации, но результаты не являются желаемыми.

Есть две проблемы:

  • Другая проблема заключается в том, что методы toString () и test () вызываются после друг друга, при запуске программы эти методы вызываются в случайном порядке **

  • Третий Проблема заключается в том, что результаты теста () отображаются не после каждых 10 000 транзакций, но часто более 10 000 транзакций

Могут ли несколько потоков обращаться к одному и тому же методу deposit () илиdraw () из одного класса? в то же время, несмотря на то, что методы используют методы "lock.lock () и" lock.unlock () "?

Почему здесь есть какое-то отклонение? Как я могу убедиться, что общий баланс всегда одинаковы?

Вот класс Account:

import java.util.concurrent.locks.*;

class Account {
  private int balance;
  private int accountNumber;
  private Lock lock;
  private Condition lockCondition;

  public Account(int accountNumber, int balance) {
    this.accountNumber = accountNumber;
    this.balance = balance;
    this.lock = new ReentrantLock();
    this.lockCondition = lock.newCondition();
  }


/* HERE IS THE WITHDRAW AND DEPOSIT METHODS THAT ARE LOCKED */
  void withdraw(int amount) { 
      lock.lock(); // Acquire the lock
      try {
        while (balance < amount) {
          lockCondition.await();
        }
        balance -= amount;
      }
      catch (InterruptedException ex) {
        ex.printStackTrace();
      }
      finally {
        lock.unlock(); // Release the lock
      }
  }

  void deposit(int amount) {
      lock.lock(); // Acquire the lock
      try {
        balance += amount;
        // Signal thread waiting on the condition
        lockCondition.signalAll();
      }
      finally {
        lock.unlock(); // Release the lock
      }
  }

  int getAccountNumber() {
    return accountNumber;
  }

  public int getBalance() {
    return balance;
  }

  Lock getLock() {
      return lock;
  }
}

Вот класс AccountThread:

import java.util.Random;

class AccountThread extends Thread {
  private Bank bank;
  private boolean debug;
  private int accountIndex;
  private int maxTransferAmount;
  private Random random;

  public AccountThread(Bank b, int index, int max, boolean debug) {
    this.bank = b;
    this.accountIndex = index;
    this.maxTransferAmount = max;
    this.debug = debug;
    this.random = new Random();
  }

  public void run() {
    try {
      while (!interrupted()) {
        for (int i = 0; i < maxTransferAmount; i++) {
            int toAccount = random.nextInt(bank.nrOfAccounts());
            int amount = random.nextInt(maxTransferAmount);
            bank.transfer(accountIndex, toAccount, amount);
            sleep(2);
        }
      }
    } catch (InterruptedException ignored) {
    }
  }
}

Вот класс банка:

import java.util.concurrent.locks.*;

class Bank {
  private static final int TEST_FREQUENCY = 10000;
  private static final int TO_STRING_FREQUENCY = 10000;
  private Lock lock;
  private int deviationCount;
  private int initialBalance;
  private Account[] accounts;
  private long transactionCount;
  private boolean debug;
  private int testCount;


  public Bank(int accountAmount, int initialBalance, boolean debug) {
    accounts = new Account[accountAmount];
    this.initialBalance = initialBalance;
    this.debug = debug;
    int i;
    for (i = 0; i < accounts.length; i++)
      accounts[i] = new Account(i, initialBalance);
    this.transactionCount = 0;
    this.deviationCount = 0;
    this.lock = new ReentrantLock();
  }

  public void transfer(int fromAccount, int toAccount, int amount) {
      accounts[fromAccount].withdraw(amount);
      accounts[toAccount].deposit(amount);
      this.transactionCount++;
//    accounts[fromAccount].getLock().lock();
//    accounts[toAccount].getLock().lock();
    //  for (Account account: accounts) {account.getLock().lock();} 
      lock.lock();
      try {
          if (transactionCount % TEST_FREQUENCY == 0) {
              test();
          }
          if (transactionCount % TO_STRING_FREQUENCY == 0) {
              System.out.println(toString());
          }

    //    accounts[fromAccount].getLock().unlock();
//        accounts[toAccount].getLock().unlock();
      } finally {
          lock.unlock();
      }
//    for (Account account: accounts) {account.getLock().unlock();}
  }


  public void test() {
    int sum = 0;
    for (Account account : accounts) {sum += account.getBalance(); }

    if (sum != nrOfAccounts()*initialBalance) {deviationCount++; }

    System.out.println("Transactions:" + getTransactionCount() + " Balance: " + sum
            + " Deviation count: " + getDeviationCount());
    testCount++;
  }

  @Override
  public String toString() {
      String string = String.format("\nTransactions; %d%n"
            + "Initial balance: %d%nNumber of accounts: %d%n"
            + "Deviation count: %d%nTestCount: %d%n"
            + "Error percentage: %.2f%n", 
            getTransactionCount(), initialBalance, nrOfAccounts(), 
            getDeviationCount(), testCount, getErrorPercentage());
      if (debug) {
          for (Account account :accounts) {
              string = string.concat(String.format("Account nr.: %d, Balance: %d%n", 
                      account.getAccountNumber(), account.getBalance()));
          }
      }
      return string;
  }

  int nrOfAccounts() {
    return accounts.length;
  }

  private long getTransactionCount() {
      return transactionCount;
  }

  private int getDeviationCount() {
      return deviationCount;
  }

  private double getErrorPercentage() {
      double dividend = getDeviationCount();
      double divisor = testCount;
      double result = dividend / divisor;
      return result;
  }
}

Вот класс BankTest:

    public class BankTest {
    private static final boolean DEBUG = true;
    private static final int ACCOUNT_AMOUNT = 10;
    private static final int INITIAL_BALANCE = 100000;

    public BankTest() {};

    public static void main(String[] args) {
        Bank b = new Bank(ACCOUNT_AMOUNT, INITIAL_BALANCE, DEBUG);
        int i;
        for (i = 0; i < ACCOUNT_AMOUNT; i++) {
            AccountThread t = new AccountThread(b, i,
                    INITIAL_BALANCE, DEBUG);
            t.setPriority(Thread.NORM_PRIORITY + i % 2);
            t.start();
        }
    }
}

1 Ответ

0 голосов
/ 29 апреля 2020

В: Что происходит, когда идет test() в процессе выполнения, а некоторые другие AccountThread вычитают деньги с одного счета, который уже был выбран в тесте, и переводят его на счет, который еще не был взят в тест?

A: Деньги, которые были перемещены, будут засчитаны дважды.

У вас возникла обратная проблема, если деньги переводятся со счета, который еще не был выбран, на счет, который уже был выбран. Эти деньги не будут учитываться вообще.

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

IMO: Лучший способ сделать это - использовать ReadWriteLock. Обычный вариант использования ReadWriteLock - это общая структура данных, которую потоки часто хотят исследовать, но редко хотят обновлять. Это позволит любому количеству одновременно «читателей» получить доступ к структуре * ИЛИ * это позволит одному «писателю» получить доступ к ней, но не обоим.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...