使用Java同步的银行模拟器

时间:2015-09-16 03:59:47

标签: java multithreading synchronization thread-synchronization

我有一个方法转移()从一个账户提取资金并将其存入另一个账户。每个运行10个帐户都有自己的主题。我有另一种方法测试(),它总结了每个帐户的总数,以确保银行没有丢失或赚钱。为了获得准确的总数,我创建了一个布尔标志来指示测试是否正在进行中。如果是,我需要以某种方式暂停转移,直到测试结束。我尝试使用synchronized块来实现这一点,以告诉线程在条件上等待并在条件不再为真时释放。出于某种原因,我遇到了困难。我的转移方法如下:

public class Bank {

    public static final int NTEST = 10;
    private Account[] accounts;
    private long ntransacts = 0;
    private int initialBalance;
    private int numAccounts;
    private boolean open;
    private int transactsInProgress;
    private boolean testing=false;

    public Bank(int numAccounts, int initialBalance) {
        open = true;
        this.initialBalance = initialBalance;
        this.numAccounts = numAccounts;
        accounts = new Account[numAccounts];
        for (int i = 0; i < accounts.length; i++) {
            accounts[i] = new Account(this, i, initialBalance);
        }
        ntransacts = 0;
        transactsInProgress = 0;
    }
    public synchronized void incrementTransacts(){
        transactsInProgress++;
    }
    public synchronized void decrementTransacts(){
        transactsInProgress--;
    }

    public void transfer(int from, int to, int amount) throws InterruptedException {

    accounts[from].waitForAvailableFunds(amount);
    synchronized(this){
        while(testing){
            System.out.println("Cannot transfer while testing...");
            this.wait();
        }
    }
        if (!open) return;
        if (accounts[from].withdraw(amount)) {
            incrementTransacts(); //synchronzied method increments transactsInProgress
            accounts[to].deposit(amount);
            decrementTransacts(); //synchronized method
        }
        if (shouldTest()) test();

    synchronized(this){
        this.notifyAll();
    }    
    }

    public synchronized void test() throws InterruptedException {
        int sum = 0;

        testing=true;
        while(transactsInProgress!=0){
                System.out.println("Cannot test while transactions are in progres... \nWaiting...");
            wait();
        }

        for (int i = 0; i < accounts.length; i++) {
            System.out.printf("%s %s%n", 
                    Thread.currentThread().toString(),accounts[i].toString());
            sum += accounts[i].getBalance();
        }
        System.out.println(Thread.currentThread().toString() + 
                " Sum: " + sum);
        if (sum != numAccounts * initialBalance) {
            System.out.println(Thread.currentThread().toString() + 
                    " Money was gained or lost");
            System.exit(1);
        } else {
            System.out.println(Thread.currentThread().toString() + 
                    " The bank is in balance");
        }
        testing=false;
        notifyAll();
    }
       public int size() {
        return accounts.length;
    }

    public synchronized boolean isOpen() {return open;}

    public void closeBank() {
        synchronized (this) {
            open = false;
        }
        for (Account account : accounts) {
            synchronized(account) {
                account.notifyAll();
            }
        }
    }

    public synchronized boolean shouldTest() {
        return ++ntransacts % NTEST == 0;
    }
}

自从我用Java编写代码以及我对线程和并发性的新手以来已经有一段时间了,所以我不确定我出错的确切位置。当我运行程序时,银行金额不正确。每个账户中有10,000个,所以每次的金额应该是100,000。这里有什么想法吗?

编辑:线程类和主要:

class TransferThread extends Thread {

    public TransferThread(Bank b, int from, int max) {
        bank = b;
        fromAccount = from;
        maxAmount = max;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            int toAccount = (int) (bank.size() * Math.random());
            int amount = (int) (maxAmount * Math.random());
            bank.transfer(fromAccount, toAccount, amount);
        }
        bank.closeBank();
    }
    private Bank bank;
    private int fromAccount;
    private int maxAmount;
}

主:

public static void main(String[] args) throws InterruptedException {
    Bank b = new Bank(NACCOUNTS, INITIAL_BALANCE);
    Thread[] threads = new Thread[NACCOUNTS];
    // Start a thread for each account
    for (int i = 0; i < NACCOUNTS; i++) {
        threads[i] = new TransferThread(b, i, INITIAL_BALANCE);
        threads[i].start();
    }
    // Wait for all threads to finish
    for (int i = 0; i < NACCOUNTS; i++) {
        try {
            threads[i].join();
        } catch (InterruptedException ex) {
            // Ignore this
        }
    }
    b.test();
}

2 个答案:

答案 0 :(得分:1)

我不知道您的确切问题,但您的代码中有一些问题:

  1. 您的transfer()方法有两个不同的synchronized块,但似乎执行应在它们之间受到保护的操作。

  2. 不要信任原始boolean变量进行同步。当您使用多个线程时,应使用AtomicBoolean

  3. 现在更新,我更好地理解了这个问题:

    这里的问题是您正在尝试以其设计者无意使用的方式使用synchronized。如果你要同步,你选择一个对象并说“一次只有一个线程可以操作这个东西”。在synchronized(this)类中使用synchronized或声明方法Bank说“只有一个线程可以同时操纵银行的状态”。

    从下面的评论中,我了解情况并非如此。如果多个线程可以一次更新帐户,则银行不是您要同步的资源。

    您应该在更细粒度级别进行保护(例如,单独锁定每个帐户),或使用不同的锁定构造,例如ReadWriteLock,允许多个线程说共享较低级别的访问权限或单个线程获得独家访问权。

答案 1 :(得分:0)

这个错误并不是很明显,但这看起来不对:

    if (accounts[from].withdraw(amount)) {
        incrementTransacts(); //synchronzied method increments transactsInProgress
        accounts[to].deposit(amount);
        decrementTransacts(); //synchronized method
    }

此部分代码未同步。如果2个线程试图存入同一个帐户,它们可能会互相覆盖。或者,如果没有任何内存障碍,则可能无法正确发布正确的余额。您没有显示Account对象,但如果它不是线程安全的,则可能是问题的根源。

每个帐户都可以有一个AtomicInteger余额,然后以原子方式并以线程安全的方式进行更新。

class Account {
   private final AtomicInteger balance = new AtomicInteger(0);
   ...
   public int withdraw(int amount) {
        // loop to get the money in a thread-safe manner
        while (true) {
           // get current balance
           int current = balance.get();
           if (current < amount) {
               // not enough funds
               return 0;
           }
           // update the balance atomically if it is still current
           if (balance.compareAndSet(current, current - amount)) {
               return amount;
           }
           // someone else beat me to it so loop and get new balance
       }
   }

   public void deposit(int amount) {
       // similar but without the not-enough-funds check and with a +
   }

以下是您的代码中可能有用的其他一些问题。

  1.   

    帐户[从] .waitForAvailableFunds(量);

    您正在等待资金然后撤回资金。那里有一个竞争条件可能导致另外2个线程都返回true但只有一个线程实际上会获得资金。我们也看不到Account类来确保它是线程安全的。

    我会使用某种accounts[from].waitAndWithdraw(...)方法,等到帐户有资金然后撤回。

  2. 在我看来,如果你获得了撤销和存款命令的正确组合,你可以很容易地使你的系统陷入僵局。例如,如果发生10次撤销,他们都没有资金,您的系统就会停止。您可以使用超时执行wait(...)并在达到超时时返回错误代码(或抛出)。

    这不会发生在main(...)上,但其他用户也可以这样做。

  3. public synchronized void decrementTransacts(){ transactsInProgress--; }

    这类代码乞求使用AtomicInteger。然后,您可以在没有同步锁的情况下执行transactsInProgress.incrementAndGet()

  4. 正如@Tyler所提到的,您可能会考虑在Account对象而不是Bank上进行同步。因此Bank只是发出传输的类,但锁定将在Account对象上进行本地化,以确保它们是线程安全的。或者使用上面的代码,可能不需要许多同步锁。

  5. 很明显有时候test()会失败,因为有时您从一个帐户中提取了一笔金额,但可能没有在另一个帐户中存入相同的金额