当前位置: 首页 > 知识库问答 >
问题:

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

冀望
2023-03-14

我有一个简单的Java代码。目的是测试Java的enterLock()。有一个名为Bank的类(代码如下),它包含一个Account对象数组(Account类的代码如下)。

银行类可以使用名为“取款结束存款”的帐户对象的方法,将资金从一个帐户转移到另一个帐户。(其中一个数据成员是名为Accounts的帐户数组)。

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

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

它将初始化一个银行对象,生成10个AccountThread对象,将对该银行对象的引用传递给这些AccountThread对象。然后它将开始运行这10个线程(运行AccountThread对象)。

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

这些线程将在一个无限循环中,随机选择一个帐户(其他9个帐户中的一个),并将随机数量转移到该帐户。

在每个10 000个交易后,程序将打印一些数据,其中之一是“总和余额”(所有这10个账户的余额之和)。

这笔钱永远不应该改变,因为账户之间只会相互转账。

我用了锁。lock()和lock。在“存款”和“取款”方法中解锁(),以便当一个帐户中有存款或取款时,其他线程不应访问这些方法。

但也存在一些问题:

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

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

我试过:

1)我怀疑这个问题可能是由于线程在打印数据时仍在运行(这可能会导致数据不一致)。所以我在Bank类中改变了一些传输方法,做了一个代码来停止所有线程在test()之前运行(并在运行test()后解锁所有线程)。

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

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

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

但是结果是一样的。

2)在运行test()之前,我只锁定了传输中刚刚使用的两个帐户,这样其他线程就不会使用这些帐户。但是它也不起作用。

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

另外两个问题是:

>

  • 另一个问题是,当toString()和test()方法相互调用时,当运行程序时,这些方法以随机顺序调用**

    第三个问题是test()结果不是在每10000个事务之后显示的,而是经常超过10000个事务

    尽管方法使用"lock.lock()"和"lock.unlock()"方法,但多个线程是否可以同时从同一类访问相同的存款()或提取()方法?

    为什么这里有魔鬼?如何确保总余额始终不变?

    以下是帐户类别:

    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个答案

    卫志泽
    2023-03-14

    问:如果有一个test()正在进行,而其他一些AccountThread从一个测试已经取样的帐户中扣除钱,并将其转移到测试尚未取样的帐户,会发生什么情况?

    A:搬走的钱会被清点两次。

    如果钱从一个还没有被取样的账户转移到一个已经被取样的账户,你就有相反的问题。这笔钱根本不会被计算在内。

    到目前为止,您尝试的锁定可以确保银行中的总数在任何时刻都是正确的,但是您的test()无法在一瞬间完成。你需要关闭银行,防止在盘点期间发生任何和所有转账。

    IMO:最好的方法是使用ReadWriteLock。ReadWriteLock的正常用例是一个共享数据结构,线程经常想要检查,但很少想要更新。它将允许任意数量的同时“读取器”访问该结构*或*它将允许一个“写入器”访问它,但不能同时访问两个。

    在应用程序中,想要转移资金的线程是“读卡器”这听起来可能很奇怪,因为这些线程实际上想要更改银行结构,但关键是,您希望允许任意数量的线程同时执行它们的任务。另一方面,想要调用test()的线程,即使它不修改任何内容,也必须扮演“writer”角色,因为它必须以独占方式访问银行。在进行test()时,不允许进行任何传输。

     类似资料:
    • } 正如您所看到的,RetryHandler扩展了线程,该线程在内部列表上迭代。尽管使用了和,我还是得到了和已在其他

    • 问题内容: 我正在研究有效Java,在本书的第5项中,Joshua Bloch谈到了避免创建不必要的对象。一个示例演示了可变的Date对象,这些对象一旦计算出其值就永远不会被修改。 这里是“坏习惯”: isBabyBoomer方法每次调用时都不必要地创建新的Calendar,TimeZone和两个Date实例-这对我来说显然很有意义。 这里是改进的代码: 初始化时,Calendar,TimeZon

    • 问题内容: 我一直在四处张望,我至少找到了一个不清楚的答案。 我正在使用GUI构建一个非常基本的聊天应用程序,并且已经将GUI与连接对象分离了。现在,我需要在服务器类中从GUI调用一种方法,反之亦然。但是我不太了解如何做到这一点(即使使用“ this”也是如此)。这是一部分代码的样子(这是一个名为server_frame的类): 这是来自server_frame的代码,srv是另一个类(服务器)中

    • 问题内容: 我使用的不是可重入的库(用C编写)(即库中没有函数可重入)。假设我已经通过System.load加载了库以获取句柄“ v”。由于重入问题(尝试过但无意义的结果),我无法在两个线程中使用v。我可以使用锁,但这会破坏我本可以获得的任何并行性。 我想做的是启动两个线程,然后在每个线程中加载库以获取两个不同的句柄(因此,加载的库有两个副本)。 这在Java中可行吗?问候Saptarshi 问题

    • 问题内容: 和 第二个代码产生了一个空指针异常,该怎么做才能使下一个等效? 问题答案: 我可以看到,如果players某个自定义java.lang.Iterable的get()实现的实现被破坏,或者至少以一种异常的方式(与的行为不同),就会发生这种情况。 除此之外,我唯一能想到的就是您未在代码中向我们展示的某些内容导致了某些错误。 如果执行此操作会怎样?

    • 问题内容: 当与MySQL数据库连接时,我有几种方法可以做同样的事情,保存或加载不同类型的参数。目前,我对每种类型都有不同的方法。如何合并这些方法,以便它们支持不同的类型? 下面是两个非常相似但使用不同类型的方法的示例: 请注意,在该示例中,类型均为数字。在类型完全不同的情况下(例如int和String),如何避免使用近乎重复的方法? 问题答案: 您可以在此处应用 策略 模式。 …