线程银行转账模拟和同步
我有以下情况。我有一个帐户类,每个帐户都有一个余额,可以将钱转入其中。
public class Account {
private int balance;
public Account() {
this.balance = 0;
}
public void transfer(int amount) {
this.balance += amount;
}
@Override
public String toString() {
return "Account (balance: " + balance + ")";
}
}
我有一个转会经理。转账只需两个账户和一笔转账金额。传输管理器可以通过将其添加到数组列表(一种传输队列)来发出传输。将所有传输添加到队列后,可以调用performTransfers 方法,该方法在每次传输时调用performTransfer 方法。
import java.util.ArrayList;
public class TransferManager {
private ArrayList<Transfer> openTransfers;
private int issuedTransfers;
private int performedTransfers;
public TransferManager() {
openTransfers = new ArrayList<Transfer>();
issuedTransfers = 0;
performedTransfers = 0;
}
public void issueTransfer(Account from, Account to, int amount) {
openTransfers.add(new Transfer(from, to, amount));
issuedTransfers++;
}
public void performTransfers() {
for(Transfer transaction : openTransfers) {
transaction.performTransfer();
performedTransfers++;
}
openTransfers.clear();
}
@Override
public String toString() {
return "TransferManager (openTransfers: " + openTransfers.size() + "; issuedTransfers: " + issuedTransfers + "; performedTransfers: " + performedTransfers + ")";
}
private static class Transfer {
private Account from, to;
private int amount;
public Transfer(Account from, Account to, int amount) {
this.from = from;
this.to = to;
this.amount = amount;
}
public void performTransfer() {
from.transfer(-amount);
to.transfer(amount);
}
}
}
现在我添加多线程:
import java.util.Random;
public class BankingTest extends Thread {
private Account[] accounts;
private static Random random = new Random();
public BankingTest(Account[] accounts) {
this.accounts = accounts;
}
public void run() {
final TransferManager manager = new TransferManager();
//simulate some transfers
for(int i = 0; i < accounts.length; i++) {
final int index = i;
Thread thread = new Thread() {
public void run() {
try {
for(int j = 0; j < 10; j++) {
manager.issueTransfer(accounts[index], accounts[(index+1)%accounts.length], 100);
Thread.sleep(random.nextInt(10));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread.start();
}
//wait a bit
try {
Thread.sleep(60);
} catch (InterruptedException e) {
e.printStackTrace();
}
manager.performTransfers();
System.out.println(manager);
}
}
BankingTest 采用一组 10 个空白帐户。目前它尚未同步,我尝试理解为什么会出现这些错误:
Exception in thread "Thread-2" java.lang.NullPointerException
at
gp2.ha5.exercise2.TransferManager.performTransfers(TransferManager.java:23)
at gp2.ha5.exercise2.BankingTest.run(BankingTest.java:41)
and
TransferManager (openTransfers: 0; issuedTransfers: 99; performedTransfers:
99)
知道为什么我会出现这些错误以及同步在这里有何帮助?
(您可以放大以查看详细信息;))
TransferManager:http://pastebin.com/Je4ExhUz
BankTest:http://pastebin.com/cdpWhHPb
开始:http://pastebin.com/v7pwJ5T1
在我添加同步到issueTransfer和performTransfers方法后,我不断收到错误:
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
好吧,简单,所有线程,尝试执行这个方法:
但是添加到ArrayList时没有同步。您必须了解,因为列表操作不是原子的,并且因为多个线程同时访问它,所以几乎任何事情都可能发生,并且您列出的内容会被损坏。
然后,当您尝试读取列表时,您会发现其中有空元素,即使您一开始并没有要求插入空元素。这是一个示例,说明在没有正确处理的情况下访问相同的数据会如何损坏您的数据。
编辑:
因此,每当您拥有共享状态并且想要使用多个线程访问它时,您就必须同步。这不仅适用于 issuesTransfer 方法。
另一个问题是如何生成线程。 这与您最初的问题无关。
从这里的代码来看,所有线程都访问一个全局状态:用于访问数组中帐户的索引。但是主线程用for循环递增,而while线程可以随时执行。当线程启动时,索引值已发生更改的风险很高。
正如您所看到的,当您没有给予足够的重视时,并发总是会困扰您;)
Ok simple, all threads, try to execute this method :
But there is no synchronization while adding to the ArrayList. You must understand that because list operation are not atomic and because several thread accessed it at the same time, almost anything can appen and that you list become corrupted.
Then when you try to read you list, you find Null elements inside it, even you didn't requested to insert one in the first place. This is an exemple of how accessing the same data without proper handling can corrupt your data.
EDIT :
So whenever you have shared state and you want to access it using several threads, you have to synchronize. This not only for the issueTransfer method.
Another problem is how you spawn threads. This is not related to your initial problem.
From the code here all threads access one global state : the index to use for accessing accounts in your array. But the main thread increment the with the for loop, while thread may execute at any time. There is a high risk that by the time the threads are started, index value has changed.
As you can see, concurrency always bite you when you don't take enough attention to it ;)
除了 Nicolas 所说的之外 - 同步
issueTransfer
还不够,因为主线程可能已经处于performTransfers
中,并且某些线程仍可能陷入issueTransfer< /代码>。这意味着您的 ArrayList 仍然被多个线程访问。
您可以创建一个锁对象并用于
保护那些可能被多个线程触及的代码片段。
或者,您可以同步相关方法(
issueTransfer
和performTransfers
)。On top of what Nicolas said - it is not enough to synchronize
issueTransfer
, as main thread might already be inperformTransfers
and some threads can still be stuck inissueTransfer
. This means your ArrayList is still accessed by multiple threads.You can create a lock object and use
to protect those code snippets that can potentially be touched by multiple threads.
Alternatively you can make relevant methods (
issueTransfer
andperformTransfers
) synchronized.