【问题标题】:Java Nonsynchronized threads using the same resource使用相同资源的 Java 非同步线程
【发布时间】:2015-04-23 18:57:04
【问题描述】:

我必须制作一个非常简化的联合银行账户程序(在此示例中,有 3 个用户都可以访问银行账户资源),但我无法正确使用 Java 线程。

这是程序的工作方式。有些“用户”都可以访问一个具有任意设置初始余额的联合银行账户(我使用了 5000)。每人一次可以取款或存款(取款或存款每次随机产生)3次。

他们存款或取款的金额也是随机生成的,唯一的规则是金额不能超过当前余额的1/3。

最后,在每个事务之后,当前线程必须“等待”1 到 10 秒之间的随机秒数。

现在是令人困惑的部分。我们的老师要求我们创建一个独特的 NotEnoughBalance 异常类,以防某个用户以某种方式提取的钱比当前帐户中的钱多(但这是我的第一个困惑点:由于 1/3rd,理论上这永远不会发生规则)。

下面是粘贴在 pastebin 上的完整代码:

http://pastebin.com/Upam56NF

目前,当我运行主程序时:

public class BankAccount{
    public static void main(String[] args) throws InterruptedException{

        int capital = 5000;
        JointBankAccount acc = new JointBankAccount(capital);

        Thread t1 = new Thread(new Owner("Josh", acc));

        Thread t2 = new Thread(new Owner("Wade", acc));

        Thread t3 = new Thread(new Owner("Ben", acc));

        System.out.println(capital);
        String tname = Thread.currentThread().getName();
        System.out.println(tname);

        t1.start();
        t2.start();
        t3.start();

        t1.join();
        t2.join();
        t3.join();      

        for(AccountTransaction s : acc.history){
            System.out.println(s.toString());
        }
        System.out.println(acc.getBalance());
    }

}

我有时会随机在 System.out.println(s.toString()) 处遇到 NPE 异常。

如果我将存款和取款功能同步,这是完全可以解决的。

问题是,不知何故,我认为让他们同步违背了我们老师所要求的目的。如果我让它们同步,那么我觉得我确保每次提款都能成功遵循 1/3 规则,并且永远不会存在余额不足异常。

当我删除同步时我得到 NPE 的事实也让我认为错误可能在于我在异常发生时没有正确处理异常。我不知道。

任何帮助将不胜感激。

【问题讨论】:

  • 如果你没有synchronized 任何东西,那么就没有原子性或可见性保证。你几乎肯定有种族危险。
  • 有时有些东西是永远不会扔的。有人见过InternalErrorUnknownError 被抛出吗?
  • 你的意思是在运行这个程序时?没有
  • @user1966576 我的意思是,正确同步,将NotEnoughBalanceException 添加到您的代码中但永远不要抛出它(即在false == true 时抛出它)。
  • 如果我更改规则以便我在开始时保留 5000 资本,但提取或存入的金额可以是 3000 之间的随机数(因此不再有 1/3 规则),则错误正确发生并被抛出。我刚试过。我想我的老师可能把作业给错了。就像他希望发生种族危险,但感觉如果不同步存取款,程序就没有任何意义,这使得种族危险永远不会发生(使用 1/3 规则)。所以idk。

标签: java multithreading


【解决方案1】:

在我看来,这像是理解原子事务的一个很好的练习。

但首先是 NPE:一个 JointBankAccount 实例在多个线程之间共享,因此 JointBankAccount 包含的任何内容都像 history 列表一样。 history 列表属于 ArrayList 类型,它不是线程安全的,即如果两个线程同时调用 add 方法,则列表会中断。不过这很容易解决:List<AccountTransaction> history = Collections.synchronizedList(new ArrayList<AccountTransaction>());

现在是原子事务。由于任何线程都可以随时更改余额,因此在您读取余额的那一刻,余额已经过时了。 IE。 if (balance > 1000) then updateBalance() 之类的声明无效,因为余额可能在 then 之后发生变化。避免这种情况的一种方法是同步所有内容(尽管即使那样你也必须小心,例如使用AomticInteger 来注册余额)。但是NotEnoughBalanceException 意味着使用ReentrantReadWriteLock 的不同工作方式。在更新余额之前,使用读锁读取最新的余额并应用任何规则来确定余额是否可以更新。这使您可以通知“所有者”余额可能会更新。但由于您使用了读锁,您无法确定:余额可能已经更新。现在使用写锁并再次应用规则。这一次您可以确定余额值(它只能由具有单个写锁的代码更新)并且您可能会发现余额不好(其中一个规则失败)。这就是NotEnoughBalanceException 发挥作用的地方:您向“所有者”承诺可以更新余额,但现在您拥有最终写锁,您发现余额无法更新(原子事务无法完成)。

这遵循一个非常常见的模式:使用“廉价”锁来确定事务是否可以完成(执行预检查),然后使用“昂贵”锁来执行事务,但始终假设事务可能失败并重试在适当的时候进行交易。

【讨论】:

    猜你喜欢
    • 2014-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-11
    • 1970-01-01
    • 1970-01-01
    • 2011-01-16
    • 1970-01-01
    相关资源
    最近更新 更多