尽管在 Java 代码中使用了 lock(),但两个线程使用相同的方法?

Two threads use same method despite using lock() in Java code?

我在Java中有一个简单的代码。目的是测试Java的entryLock()。有一个名为 Bank 的 class(下面的代码),它包含一个帐户对象数组(下面是帐户 class 的代码)。

Bank class 可以使用名为 withdraw end deposit 的 Account 对象的方法将钱从一个帐户转移到另一个帐户。 (其中一个数据成员是名为 accounts 的帐户数组)。

AccountThread 扩展 Thread,它将 运行 每个帐户作为一个线程。它包含对 "Bank" 对象的引用。

TestBank 具有 (public static void main(String args[]) 方法并将 运行 对象。

它将初始化一个 Bank 对象,创建 10 个 AccountThread 对象,将对该 Bank 对象的引用传递给这些 AccountThread 对象。然后它将开始 运行 连接所有这 10 个线程(运行连接 AccountThread 对象)。

这 10 个账户只相互转账。每个线程将 运行 自己的帐户。

这些线程将无限循环,随机选择一个账户(其他 9 个账户之一)并将随机金额转入该账户。

每完成 10 000 笔交易后,程序会打印一些数据,其中之一是 "sum balance"(所有这 10 个账户的余额总和)。

该金额永远不会改变,因为账户只会相互转账。

我在 "deposit" 和 "withdraw" 方法中使用了 lock.lock() 和 lock.unlock() 这样当一个帐户中有存款或取款时,其他线程应该不能访问那些方法。

但是有一些问题:

但问题是,我确实从总和中得到了一些偏差,我不明白是什么原因造成的。

我尝试使用 "AccountThread" 作为 class 实现 Runnable,但结果是一样的。

这是我尝试过的:

1) 而且我怀疑这种偏差可能是由于打印数据时线程仍然 运行ning 引起的(这可能导致数据不一致)。所以我在 Bank class 中更改了一些传输方法,编写了一个代码以在 test() 之前停止来自 运行ning 的所有线程(并在 运行ning test() 之后解锁所有线程)。

for (Account account: accounts) {account.getLock().lock();} 在调用 test() 和 toString()

之前

在 test() 和 toString() 之后我调用了

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

但是结果是一样的

2) 在运行ing test()之前,我只锁定了刚刚用于转帐的两个帐户,这样其他线程就不会使用这些帐户。但是也没用。

我也尝试过其他组合,但结果不是我想要的结果。

另外两个问题是:

尽管方法使用 "lock.lock() and "lock.unlock()" 方法,但多个线程是否可以同时从同一个 class 访问同一个 deposit() 或 withdraw() 方法?

为什么这里有些偏差?我怎样才能确保总余额始终相同?

这是帐户 class:

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 class:

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) {
    }
  }
}

这是银行 class:

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 class:

    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();
        }
    }
}

问:当有一个 test() 正在进行时会发生什么,其他人 AccountThread 从测试已经抽样的一个帐户中扣除钱,并将其转移到测试已经抽样的帐户还没有抽样吗?

答:被挪动的钱会算两次

如果资金从尚未抽样的账户转移到已经抽样的账户,您会遇到相反的问题。那个钱根本不会算。

到目前为止您尝试过的锁定确保银行中的总数在任何一个时刻始终是正确的,但是您的test()可以瞬间完成。您需要在计票期间关闭银行——阻止任何和所有次转账。

IMO:最好的方法是使用 ReadWriteLock。 ReadWriteLock 的正常用例是线程经常想要检查但很少想要更新的共享数据结构。它将允许任意数量的同时 "readers" 访问结构 *OR* 它将允许一个 "writer" 访问它,但不允许同时访问它。

在您的应用程序中,想要转移资金的线程是 "readers." 这听起来可能很奇怪,因为这些线程实际上想要 更改 银行结构,但是关键是,你想允许任意数量的人同时做他们的事情。另一方面,想要调用 test() 的线程,即使它没有修改任何内容,也必须扮演 "writer" 角色,因为它必须具有 独占 访问权限银行。 test() 正在进行时,不得允许任何传输发生。