Ari*_*deh 6 java concurrency multithreading
让我们假设我想实现一个非常简单的Bank Account类,我们想要关注并发和多线程问题,
它是一个好主意,使下面的方法synchronized,即使balance是AtomicInteger?
另一方面,如果我们将所有方法都同步化,就不再使用AtomicInteger了,对吧?
import java.util.concurrent.atomic.AtomicInteger;
public class Account {
AtomicInteger balance;
public synchronized int checkBalance(){
return this.balance.intValue();
}
public synchronized void deposit(int amount){
balance.getAndAdd(amount);
}
public synchronized boolean enoughFund(int a){
if (balance.intValue() >= a)
return true;
return false;
}
public synchronized boolean transfer_funds(Account acc, int amount){ // dest : acc
if (enoughFund(amount)){
withdraw(amount);
acc.deposit(amount);
return true;
}
return false;
}
public synchronized boolean withdraw(int amount){
if (checkBalance() < amount)
return false;
balance.getAndAdd(-1 * amount);
return true;
}
}
Run Code Online (Sandbox Code Playgroud)
对两者都是肯定的,将它同步化是一个好主意,并且不需要Atomic.
如果您仅依靠Atomic而不是同步,则可能会遇到以下问题:
if (enoughFund(amount)){
withdraw(amount);
acc.deposit(amount);
return true;
}
Run Code Online (Sandbox Code Playgroud)
因为Atomic只保证你的整数不会同时访问,这意味着即使它是由其他一些线程写的,enoughFund(amount)也能保证提供正确的值amount.但是,仅Atomic并不能保证在此行获得的值与下一行代码中的值相同,因为另一个线程可以在这两行之间执行另一个Atomic操作,从而withdraw(amount);可以在下面设置您的余额零.
将您的金额声明为AtomicInteger不会阻止线程在方法执行过程中被抢占(如果它未同步).因此,例如,如果您的方法transfer_funds没有以任何方式同步,您可能会得到意想不到的结果,即使您的金额将是AtomicInteger
public /* synchronized */ boolean transfer_funds(Account acc, int amount){ // dest : acc
if (enoughFund(amount)){
withdraw(amount); // <- thread can be preempted in the middle of method execution
acc.deposit(amount);
return true;
}
return false;
}
Run Code Online (Sandbox Code Playgroud)
这些问题被称为竞争条件.一个可能的例子是当两个线程试图从同一账户转移资金时.当一个线程确定enoughFund要执行信用转账时,该线程可能被抢占,同时其他线程可以开始从该账户转移资金.当第一个线程再次开始处理时,它不会仔细检查是否enoughFunds要执行信用转移(他已经检查过,但他的知识可能已经过时),但它会进入下一行执行.这样您可能无法获得一致的结果.您可以更改所有帐户在开头时的总金额.
Cay Horstmann的核心Java书中对这方面有一个非常好的解释 - 这里有关于免费同步的章节.它详细描述了您要问的几乎完全相同的问题.
原子数据类型向您承诺的就是提供对其值的无锁但线程安全的访问。因此,您使用AtomicIntegerover 的正当理由之一synchronized是当您只需要保护更新操作时,例如
synchronized (lockObj) {
myInt++; // slower than AtomicInteger
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,AtomicInteger.incrementAndGet()会更快。但是,如果您的同步范围大于该范围并且增量只是其中的一部分,则synchronized建议使用具有非原子整数的块(在该块内受保护)。
是的,你是对的。AtomicInteger如果对对象的所有访问都是如此synchronized(在任何给定时刻最多只有一个线程会访问其内容),则不会授予任何好处。
AtomicInteger正如其他人指出的那样,当您需要对该变量进行线程安全访问并且对其执行简单更新时,最好使用。
在本例中,您有两个复合运算transfer_funds和withdraw。前者有三个访问权限,后者有两个访问权限。
您希望这些操作本身是原子的,即,它们对其他人来说似乎是瞬时发生的,它们不能分解为更小的操作。要实现这一点,synchronized是必要的。
最后一点,我想留下一个(可能)有用的建议。您应该为每个帐户分配一个唯一的标识符。您可能会问,为什么要防止死锁。
假设我们有两个线程T1和T2,以及两个帐户a1和a2。
T1:
a1.transfer_funds(a2, 42);
Run Code Online (Sandbox Code Playgroud)
T2:
a2.transfer_funds(a1, 00101010);
Run Code Online (Sandbox Code Playgroud)
您可能会遇到以下交错:
T1 -> a1.enoughFund(42)
T1 -> a1.withdraw(42)
T2 -> a2.enoughFund(00101010)
T2 -> a2.withdraw(00101010)
T1 -> a2.deposit(42) // blocks on a2's monitor, because T2 already has it
T2 -> a1.deposit(00101010) // same as above
Run Code Online (Sandbox Code Playgroud)
两个线程都会无限期地等待对方,因为您的所有方法都是synchronized.
例如,在为每个帐户分配一个标识符时,解决方案是:
public class Account {
private int balance;
private final int id;
/* Not synchronized */
public boolean transferFunds(Account acc, int amount) {
if (id < acc.getId()) {
synchronized (this) {
synchronized (acc) {
return transfer(acc, amount);
}
}
}
else if (id > acc.getId()) {
synchronized (acc) {
synchronized (this) {
return transfer(acc, amount);
}
}
}
return true; // same id, transfering to self has no effect.
}
private boolean transfer(Account acc, int amount) {
if (balance >= amount) {
balance -= amount;
// This is not synchronized, you may make it private.
acc.depositUnsynchronized(amount);
return true;
}
return false;
}
}
Run Code Online (Sandbox Code Playgroud)
上面实现了有序的锁获取,因此,无论哪种情况,所有线程都会先尝试获取id最低的账户。如果该帐户正在进行转账,则在第一个转账结束之前不会发生其他转账。
如果你非常想使用AtomicInteger,你可以这样写:
public class Account {
private final AtomicInteger balance = new AtomicInteger(0);
public void deposit(int amount) {
balance.getAndAdd(amount);
}
public boolean withdraw(int amount) {
for (int i; i < SOME_NUMBER_OF_ATTEMPTS; ++i) {
int currentBalance = balance.get();
if (currentBalance < amount) return false;
boolean updated = balance.compareAndSet(currentBalance, currentBalance - amount);
if (updated) return true;
}
}
public boolean transfer(int amount, Account recipient) {
boolean withdrawn = withdraw(amount);
if (withdrawn) recipient.deposit(amount);
return withdrawn;
}
}
Run Code Online (Sandbox Code Playgroud)
这是安全的,它不使用锁。进行转账或取款的线程并不能保证一定会完成,但是嘿。
循环比较并设置的技术是一种标准技术。这就是所使用的锁synchronized本身是如何实现的。