减去值时无法控制 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
。但是交易彼此不知道。第一笔交易将余额减少 300
至 200
。然后第二笔交易将余额降低到-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;
}
}
这是我第一次在 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
。但是交易彼此不知道。第一笔交易将余额减少 300
至 200
。然后第二笔交易将余额降低到-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;
}
}