减去值时无法控制 AtomicInteger 的原子行为

not able to control atomic behavior of AtomicInteger while subtracting value

这是我第一次在 multithreading 中使用 java.util.concurrent.atomic 包进行同步。我试图通过 java.util.concurrent.atomic 处理 OCP 的 Kathy Sierra 书中给出的 classic 示例 AccountDanger class。但是我无法保护 balance 变量,即 AtomicInteger 不被撤回。下面是我的代码:

帐号class

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

账号危险class

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

我知道,我肯定有什么地方错了。谁能纠正我的解决方案..

问题在于,在您查看余额的时间点 (if(account.getBalance()>=amt) {) 和您实际取款的时间点 (account.withdraw(amt);) 之间,余额可能已经发生变化,例如降低到 <= amt.

为了清楚起见,让我们看一个具体的场景。如果您有一个余额为 500 的帐户,并且您启动了两个线程以分别提取 300,则每笔交易本身都是合法的,因为它不会透支,因此两次检查 if-语句将产生 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 keyword:

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

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

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

您没有自动使用 AtomicInteger(或者更确切地说,Account)。

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

其他线程可以改变"other stuff"内的平衡。

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

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

如果 的值仍然等于 balance,则此 AtomicInteger 设置为 balance-amt,然后 return s true。如果在您读取其值后它已更改,则 compareAndSet 将 return false.

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

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

我让你来决定如何最好地处理 withdrawIfBalanceEquals returning 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;
  }
}