2

这是我第一次java.util.concurrent.atomicmultithreading. 我试图处理AccountDangerKathy Sierra 书中为 OCP 通过java.util.concurrent.atomic. 但我无法保护balance变量,即AtomicInteger不被过度撤回。下面是我的代码:

账户类

package p1;

import java.util.concurrent.atomic.AtomicInteger;

public class Account {
  private AtomicInteger balance = new AtomicInteger(100);

  public int getBalance() {
    return balance.get();
  }

  public void withdraw(int amount) {
    balance.addAndGet(-amount);
    System.out.println("~~~~~~ " + balance);
  }
}

AccountDanger 类

package p1;

public class AccountDanger implements Runnable {
  private Account account = new Account();
  private int amt = 10;

  public void run() {

    for (int i = 0; i < 10; i++) {
      if (account.getBalance() >= amt) {
        System.out.println(Thread.currentThread().getName() + " is going to withdraw..");
        try {
          Thread.sleep(500);
        } catch (InterruptedException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
        account.withdraw(amt);
      } else {
        System.out.println("not enough balance");
      }
      if (account.getBalance() < 0) {
        System.out.println("account is over withdrawn!!!");
      }
    }
  }

  public static void main(String[] args) throws InterruptedException {
    AccountDanger ad = new AccountDanger();
    Thread t1 = new Thread(ad, "Mark");
    Thread t2 = new Thread(ad, "Phew");
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println("final balance left is : " + ad.account.getBalance());
  }
}

我知道,我肯定在某个地方错了。任何人都可以纠正我的解决方案..

4

2 回答 2

3

你没有使用AtomicInteger(或者更确切地说,Account)原子。

if(account.getBalance()>=amt){
   // Other stuff
   account.withdraw(amt);

其他线程可以改变“其他东西”内部的平衡。

Account中,您需要这样的方法,使用AtomicInteger.compareAndSet

boolean withdrawIfBalanceEquals(int balance, int amt) {
  return this.balance.compareAndSet(balance, balance - amt);
}

如果它的值仍然等于,这只会设置AtomicIntegerto ,然后返回。如果在您读取其值后它已更改,将返回.balance-amtbalancetruecompareAndSetfalse

然后,在 中AccountDanger,您可以像这样使用它:

int balance = account.getBalance();
if (balance >= amt) {
  // Other stuff.
  account.withdrawIfBalanceEquals(balance, amt);
}

我让你决定如何最好地处理withdrawIfBalanceEquals返回的情况false;一种方法是:

while (true) {
  int balance = account.getBalance();
  if (balance >= amt) {
    // Other stuff.
    if (account.withdrawIfBalanceEquals(balance, amt)) {
      // If true, we don't have to keep looping: we withdrew the
      // balance.
      break;
    }

    // If false, keep going: you'll read the balance again,
    // check if it's still greater than balance etc.

  } else {
    System.out.println("Not enough balance");
    break;
  }
}
于 2018-01-04T10:54:29.383 回答
1

问题是在您检查余额if(account.getBalance()>=amt) {的时间点 () 和您实际提取的时间点 ( account.withdraw(amt);) 之间,余额可能已经改变,例如降低到一个金额<= amt

为了清楚起见,让我们看一个具体的场景。如果您有一个余额为 的帐户,500并且您启动了两个线程分别提取300,则每笔交易本身都是合法的,因为它不会透支,因此对if-statement 的两个检查都会产生true。但交易彼此不知道。第一笔交易将余额降低300200. 然后第二笔交易将余额降低到-100

要解决此问题,您需要引入一些同步。一种可能性是在提款前检查余额:

public void withdraw (int amount) {
    if (balance.get() > amount) {
        balance.addAndGet(-amount);
        System.out.println("~~~~~~ "+balance);
    } else {
        throw new InsufficientBalanceException();
    }
}

如您所见,我引入了一个Exception表示事务无法执行的信号。你可以自由地为这个案例引入你自己的代码。这缩短了检查和实际更改之间的时间,但并不能完全解决问题。为此,您必须确保当一个线程检查并可能修改余额时,没有其他线程可以。强制执行此操作的一种方法是synchronized关键字

public synchronized void withdraw(int amount) {
    if (balance.get() > amount) {
        balance.addAndGet(-amount);
        System.out.println("~~~~~~ "+balance);
    } else {
        throw new InsufficientBalanceException();
    }
}

现在,一次只有一个线程可以调用withdraw(...)一个实例。

正如@AndyTurner 指出的那样,如果你已经有一个线程安全的数据结构,比如 anAtomicInteger并且必须synchronize围绕它,那么可能会有一些可疑的东西。an 上的每个单个操作AtomicInteger都是原子的,但不是顺序的多个操作,这就是我们必须引入一些同步的原因。在这种特殊情况下,您可以摆脱AtomicInteger并将其替换为volatile int.

于 2018-01-04T10:51:29.303 回答