2011-05-08 14 views
1

我有以下情况。我有一个帐户类,每个帐户有一个余额和钱可以转移到它。螺纹银行转帐模拟和同步

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

(你可以放大查看细节;))

TransferManager:http://pastebin.com/Je4ExhUz

BankTest:http://pastebin.com/cdpWhHPb

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


后我加入了同步到issueTransfer和performTransfers方法我不断收到错误:

enter image description here

+0

没有行号就难以阐述。你能否考虑在代码片段中添加行号(即使你的屏幕截图有不同的行号也不是引用的异常)? – mindas 2011-05-08 11:06:08

+0

好吧,我已经添加了源代码粘贴。 – 2011-05-08 11:09:15

回答

1

在什么尼古拉斯说顶 - 这是不够的同步issueTransfer,作为主线程可能已经在performTransfers和一些线程仍然可以卡在issueTransfer。这意味着你的ArrayList仍然被多个线程访问。

您可以创建一个锁定对象,并使用

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

来保护这些代码片段有可能被多个线程感动。 或者,您可以使相关方法(issueTransferperformTransfers)同步。

+0

我添加了synchronized语句但仍然有错误,请看我的编辑。 – 2011-05-08 11:25:51

+0

你的主要方法是怎样的?我做了同步的方法,但我没有得到我的实例NPE。结果仍然不正确/不确定,但我无法重现的例外。 – mindas 2011-05-08 11:36:12

+0

这里是主要的方法http://pastebin.com/v7pwJ5T1 – 2011-05-08 11:37:43

2

好简单,所有的线程,尝试执行此方法:

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], 

帐户[(索引+ 1)%accounts.length], 100); Thread.sleep(random.nextInt(10)); } }赶上(InterruptedException的E){

    e.printStackTrace(); 
       } 
      } 
     }; 
     thread.start(); 

从这里所有的代码线程访问一个全局状态:该指数用于您的访问阵列中的账户。但是主线程用for循环递增,而线程可以在任何时候执行。在线程启动时,风险很大,索引值已经改变。

正如你所看到的,并发总是咬你的时候你不采取足够重视;)

+0

我明白,但添加一个同步到该方法头没有效果,我不断收到相同的错误 – 2011-05-08 11:10:21

+0

添加同步到该方法将阻止并行执行的方法。但是你有几个方法可以做到,所以你必须在列表上进行同步。 (同步块或使用并发列表帮助集合#...) – Omnaest 2011-05-08 11:14:53

+0

使用并发列表可能是一个坏主意。它将对给定的代码有所帮助,但是类将会非常脆弱,因为其他变量(例如'issuedTransfers'和'performTransfers')将不会同步。 – mindas 2011-05-08 11:20:02