java - 线程银行转账模拟和同步

标签 java multithreading

我有以下情况。我有一个账户类,每个账户都有余额,可以向其转账。

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)

知道为什么我会收到这些错误以及同步如何帮助解决这个问题吗?

enter image description here

(您可以放大查看详细信息;))

传输管理器:http://pastebin.com/Je4ExhUz

银行测试:http://pastebin.com/cdpWhHPb

开始:http://pastebin.com/v7pwJ5T1


在我将同步添加到 issueTransfer 和 performTransfers 方法后,我不断收到错误:

enter image description here

最佳答案

Ok 很简单,所有线程,尝试执行这个方法:

public void issueTransfer(Account from, Account to, int amount) {
    openTransfers.add(new Transfer(from, to, amount));
    issuedTransfers++;
}

但添加到ArrayList 时没有同步。您必须了解,因为列表操作不是原子的,并且因为有多个线程同时访问它,所以几乎所有内容都可以附加并且您列出的内容会损坏。

然后,当您尝试读取您的列表时,您会在其中找到 Null 元素,即使您一开始并没有要求插入一个元素。这是一个示例,说明在未经适当处理的情况下访问相同数据会如何损坏您的数据。

编辑:

因此,每当您拥有共享状态并且想要使用多个线程访问它时,您就必须进行同步。这不仅适用于 issueTransfer 方法。

另一个问题是如何生成线程。 这与您最初的问题无关。

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

从此处的代码中,所有线程都访问一个全局状态:用于访问数组中帐户的索引。但是主线程使用 for 循环递增,而线程可以随时执行。很有可能在线程启动时索引值已更改。

如您所见,当您没有对它给予足够的重视时,并发总是困扰您 ;)

关于java - 线程银行转账模拟和同步,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5926873/

相关文章:

c++ - libavcodec 返回的损坏的 AVFrame

multithreading - 如何使用 Node.js 创建多线程应用程序,访问 LevelDB?

java - 如何通过imageview为 GridView 设置事件

java - 如何获取json的索引并在pojo类中使用它

java - 有人可以解释一下这个左旋转数组代码是如何工作的吗?

java - 对于 Java,Class.newInstance() 是线程安全的

java - 如何将 JSpinner 设置为不可编辑?

java - 使用 junit 测试同步 api

c# - 了解 async/await 以管理多个客户端

multithreading - Julia:宏线程和并行