线程银行转账模拟和同步

发布于 2024-11-05 21:47:52 字数 4133 浏览 0 评论 0 原文

我有以下情况。我有一个帐户类,每个帐户都有一个余额,可以将钱转入其中。

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方法后,我不断收到错误:

在此处输入图像描述

I have following situation. I have a class for accounts, each account has a balance and money can be transfered to it.

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 + ")";
    }
}

And I have a transfer manager. A transfer simply takes two accounts and a amount of money to be transfered. The transfer manager can issue transfers by adding it to the array list, kind of transfer queue. After all transfers are added to the queue the performTransfers method can be called which calles the performTransfer method on every transfer.

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

Now I add multithreading:

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 takes an array of say 10 blank accounts. For now it is not synchronized and I try to understand why I get those errors:

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)

Any idea why I get those errors and how can synchronization help here?

enter image description here

(you can zoom in to see the details ;))

TransferManager: http://pastebin.com/Je4ExhUz

BankTest: http://pastebin.com/cdpWhHPb

Start: http://pastebin.com/v7pwJ5T1


After I added the synchronized to issueTransfer and performTransfers method I keep getting errors:

enter image description here

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

小伙你站住 2024-11-12 21:47:52

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

public void issuesTransfer(账户来自,账户至,int amount) {
    openTransfers.add(new Transfer(从、到、金额));
    已发出转账++;
}

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

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

编辑:

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

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

 //模拟一些传输
    for(int i = 0; i 

账户[(index+1)%accounts.length],
100);
Thread.sleep(random.nextInt(10));
}
} catch (InterruptedException e) {

 e.printStackTrace();
                }
            }
        };
        线程.start();

从这里的代码来看,所有线程都访问一个全局状态:用于访问数组中帐户的索引。但是主线程用for循环递增,而while线程可以随时执行。当线程启动时,索引值已发生更改的风险很高。

正如您所看到的,当您没有给予足够的重视时,并发总是会困扰您;)

Ok simple, all threads, try to execute this method :

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

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.

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

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

醉城メ夜风 2024-11-12 21:47:52

除了 Nicolas 所说的之外 - 同步 issueTransfer 还不够,因为主线程可能已经处于 performTransfers 中,并且某些线程仍可能陷入 issueTransfer< /代码>。这意味着您的 ArrayList 仍然被多个线程访问。

您可以创建一个锁对象并用于

synchronized (lock) {
   // vulnerable code goes here
}

保护那些可能被多个线程触及的代码片段。
或者,您可以同步相关方法(issueTransferperformTransfers)。

On top of what Nicolas said - it is not enough to synchronize issueTransfer, as main thread might already be in performTransfers and some threads can still be stuck in issueTransfer. This means your ArrayList is still accessed by multiple threads.

You can create a lock object and use

synchronized (lock) {
   // vulnerable code goes here
}

to protect those code snippets that can potentially be touched by multiple threads.
Alternatively you can make relevant methods (issueTransfer and performTransfers) synchronized.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文