尽管在 Java 代码中使用了 lock(),但两个线程使用相同的方法?
Posted
技术标签:
【中文标题】尽管在 Java 代码中使用了 lock(),但两个线程使用相同的方法?【英文标题】:Two threads use same method despite using lock() in Java code? 【发布时间】:2020-04-28 18:55:42 【问题描述】:我有一个简单的 Java 代码。目的是测试Java的entryLock()。有一个名为 Bank 的类(代码如下),其中包含一个 Account 对象数组(Account 类的代码如下)。
Bank 类可以使用名为withdraw end deposit 的Account 对象的方法将钱从一个Account 转移到另一个Account。 (其中一个数据成员是名为 accounts 的 Accounts 数组)。
AccountThread 扩展了 Thread,它将每个帐户作为一个线程运行。它包含对“银行”对象的引用。
TestBank 具有 (public static void main(String args[]) 方法并将运行对象。
它将初始化一个 Bank 对象,创建 10 个 AccountThread 对象,将对该 Bank 对象的引用传递给这些 AccountThread 对象。然后它将开始运行所有这 10 个线程(运行 AccountThread 对象)。
这 10 个帐户只相互转账。每个线程将运行自己的帐户。
这些线程将无限循环,随机选择一个帐户(其他 9 个帐户之一)并将随机金额转移到该帐户。
在每 10 000 笔交易后,程序会打印一些数据,其中之一是“总余额”(所有这 10 个帐户的余额之和)。
这笔金额永远不会改变,因为账户只是相互转账。
我在“存款”和“取款”方法中使用了 lock.lock() 和 lock.unlock(),这样当一个帐户中有存款或取款时,其他线程不应访问这些方法。
但是有一些问题:
但问题是,我确实得到了一些与总和的偏差,我不明白是什么原因造成的。
我尝试使用“AccountThread”作为类实现Runnable,但结果是一样的。
我试过了:
1) 而且我怀疑偏差可能是由于打印数据时线程仍在运行(这可能导致数据不一致)引起的。所以我在 Bank 类的 transfer 方法上做了一些改动,编写了一个代码来阻止所有线程在 test() 之前运行(并在运行 test() 之后解锁所有线程)。
for (Account account: accounts) account.getLock().lock();
在调用 test() 和 toString() 之前
在 test() 和 toString() 之后我确实调用了
for (Account account: accounts) account.getLock().unlock();
但结果是一样的。
2) 在运行test()之前,我只锁定了刚才在transfer中使用的两个账号,这样其他线程就不会使用这些账号了。但它也不起作用。
我也尝试过其他组合,但结果不是预期的结果。
另外两个问题是:
另一个问题是,而 toString() 和 test() 方法是 相互调用,运行程序时这些方法得到 随机调用**
第三个问题是 test() 结果显示不是在每 10 000 次之后 交易,但通常超过 10 000 笔交易
尽管方法使用“lock.lock() 和“lock.unlock()”方法,但多个线程是否可以同时从同一个类访问相同的 deposit() 或 withdraw() 方法?
这里为什么会有偏差?如何确保总余额始终相同?
这是 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】:Q:当有test()
在进行中,而其他AccountThread
从一个已经测试过采样的账户中扣款,转入测试尚未采样的账户时会发生什么?
A:移动的钱将被计算两次。
如果资金从尚未抽样的帐户转移到已抽样的帐户,则会出现相反的问题。这笔钱根本不会被计算在内。
到目前为止,您尝试的锁定确保银行中的总数始终是正确的在任何一个瞬间,但您的test()
无法一次性完成立即的。您需要在清点期间关闭银行——防止任何和所有转账。
IMO:最好的方法是使用ReadWriteLock
。 ReadWriteLock 的正常用例是线程经常想要检查但很少想要更新的共享数据结构。它将允许任意数量的同时“读者”访问该结构*或*它将允许一个“作者”访问它,但不能同时访问两者。
在您的应用程序中,想要转移资金的线程是“读者”。这可能听起来很奇怪,因为这些线程实际上想要更改 bank 结构,但重点是,您希望允许任意数量的线程同时执行它们的操作。另一方面,想要调用test()
的线程,即使它没有修改任何内容,也必须扮演“作者”角色,因为它必须独占 访问银行。在test()
进行期间不得进行任何转移。
【讨论】:
感谢您的回答。我发现了问题。 1) 'this.transactionCount++' 应该放入 transfer() 方法中的“锁定代码”(在 lock.lock() 和 lock.unlock() 之间)。 2)在 test() 方法中对所有余额求和之前,我们应该锁定所有帐户(因此在我们对所有余额求和时没有进行任何交易)(就像这样 ´account.getLock().unlock();´。3)“提款”并且“存款”方法应该在“交易”中(我不知道它是如何完成的),这样当账户被锁定时,没有“存款”就没有“提款”,所以当代码运行时不会损失任何钱 test( )。 @Coder88,“事务”描述了一种接口类型,而不是任何特定的实现或机制。事务系统的客户端可以“打开”事务,这有效地为客户端提供了系统状态的私有副本。客户端可以查询私有状态,进行更改,然后“提交”或“中止”事务。如果提交失败,或者客户端中止,那么就好像事务从未发生过一样。如果提交成功,就好像整个事情发生在一个单一的时刻(即,它“原子地”发生。)以上是关于尽管在 Java 代码中使用了 lock(),但两个线程使用相同的方法?的主要内容,如果未能解决你的问题,请参考以下文章
Java有了synchronized,为什么还要提供Lock