这是我第一次在java.util.concurrent.atomic
中使用multithreading
包进行同步。我试图通过AccountDanger
来处理Kathy 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());
}
}
我知道,我在某处肯定是错的。任何人都可以纠正我的解决方案..
答案 0 :(得分: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);
}
此仅将AtomicInteger
设置为balance-amt
,如果其值仍然等于balance
,则返回true
。如果您在阅读其值后进行了更改,则compareAndSet
将返回false
。
然后,在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;
}
}
答案 1 :(得分:1)
问题是,在您检查余额(if(account.getBalance()>=amt) {
)的时间点与实际提取的时间点(account.withdraw(amt);
)之间,余额可能已发生变化,例如降低到<= amt
。
让我们看一个明确的具体方案。如果您有一个余额为500
的帐户,并且您开始两个线程分别撤销300
,那么每个交易本身都是合法的,因为它不会透支,因此对{{1}进行两次检查} -statement将产生if
。但交易并不相互了解。第一笔交易将余额true
降低为300
。然后,第二笔交易将余额降低到200
。
要解决此问题,您需要引入一些同步。一种可能性是在退出前检查余额:
-100
正如您所看到的,我介绍了一个public void withdraw (int amount) {
if (balance.get() > amount) {
balance.addAndGet(-amount);
System.out.println("~~~~~~ "+balance);
} else {
throw new InsufficientBalanceException();
}
}
来表明交易无法执行。您可以自由地为此案例介绍您自己的代码。这缩短了检查和实际更改之间的时间,但并未完全解决问题。为此,您必须确保在一个线程检查并可能修改余额时,没有其他线程可以。强制执行此操作的一种方法是synchronized
keyword:
Exception
现在,一次只能有一个线程在一个实例上调用public synchronized void withdraw(int amount) {
if (balance.get() > amount) {
balance.addAndGet(-amount);
System.out.println("~~~~~~ "+balance);
} else {
throw new InsufficientBalanceException();
}
}
。
正如@AndyTurner指出的那样,如果你已经拥有一个线程安全的数据结构,比如withdraw(...)
并且必须围绕它AtomicInteger
,那么可能会有一些可疑的东西。 synchronize
上的每个单独操作都是原子操作,但不是顺序的多个操作,这就是我们必须引入一些同步的原因。在这种特殊情况下,您可以删除AtomicInteger
并将其替换为AtomicInteger
。