使用AtomicInteger时同步

我们假设我想实现一个非常简单的Bank Account类,我们想要关注并发和multithreading问题,

即使balanceAtomicInteger ,使以下方法synchronized是一个好主意吗?

另一方面,如果我们将所有方法都设置为同步,则不再使用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; } } 

将您的金额声明为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; } 

这些问题被称为竞争条件。 一个可能的例子是当两个线程试图从同一账户转移资金时。 当一个线程确定有enoughFund来执行信用转账时,该线程可能被抢占,同时其他线程可以开始从该账户转移资金。 当第一个线程再次开始处理时,它不会仔细检查是否有enoughFunds来执行信用转移(他已经检查过,但他的知识可能已经过时),但它会进入下一行执行。 这样您可能无法获得一致的结果。 您可以更改所有帐户在开头的总金额。

Cay Horstmann的核心Java书中对这方面有一个非常好的解释 - 这里有关于免费同步的章节 。 它详细描述了您所询问的几乎完全相同的问题。

对两者都是肯定的,使它同步是个好主意,并且不需要Atomic。

如果您仅依靠Atomic而不是同步,则可能会遇到以下问题:

  if (enoughFund(amount)){ withdraw(amount); acc.deposit(amount); return true; } 

因为Atomic只保证你的整数可以安全地同时访问,这意味着即使它被一些其他线程写入,也enoughFund(amount)保证enoughFund(amount)amount提供正确的值。 但是,仅Atomic并不能保证在此行获得的值与下一行代码中的值相同,因为另一个线程可以在这两行之间执行另一个Atomic操作,从而导致withdraw(amount); 能够将您的余额设置为零以下。

所有primefaces数据类型都承诺你要给它一个无线程安全的访问它的值。 因此,使用AtomicInteger不是synchronized一个正当理由是,您需要仅保护更新操作

 synchronized (lockObj) { myInt++; // slower than AtomicInteger } 

在这种情况下, AtomicInteger.incrementAndGet()会更快。 但是,如果您的同步范围大于该范围且增量只是其中的一部分,则建议使用具有非primefaces整数的synchronized块(在该块内受保护)。

是的,你是对的。 如果对对象的所有访问都是synchronizedAtomicInteger不会给予任何好处(在任何给定时刻,最多只有一个线程将访问其内容)。

正如其他人所指出的那样,当您需要对该变量进行线程安全访问并且对其执行简单更新时,最好使用AtomicInteger
在这种情况下,您有两个复合操作, transfer_fundswithdraw 。 前者有三个访问,后者有两个访问。

您希望这些操作本身就是 primefaces的它们看起来像其他人一样瞬间发生,它们不能在较小的操作中被分解。 要实现这一点, synchronized是必要的。


最后,我想留下一个(可能)有用的建议。 您应该为每个帐户分配一个唯一标识符。 您可能会问,为什么要防止死锁。

假设我们有两个线程, T1T2 ,以及两个账户a1a2

T1

 a1.transfer_funds(a2, 42); 

T2

 a2.transfer_funds(a1, 00101010); 

您可能会遇到以下交错:

 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 

两个线程无限期地等待彼此,因为所有方法都是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; } } 

以上实现了有序锁定获取,因此,无论如何,所有线程都将首先尝试获取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; } } 

这是安全的,它不使用锁。 转移或撤回的线程不能保证完成这样做,但是嘿。

循环比较和设置的技术是标准的。 这就是synchronized使用的锁本身是如何实现的。