我最近试图围绕一些Java多线程概念,并编写了一小段代码来帮助我理解内存可见性并尽可能正确地进行同步。基于我所读到的内容,似乎我们锁定的代码量越小,我们的程序就越有效(通常)。我写过一个小班,帮助我理解可能遇到的一些同步问题:
public class BankAccount {
private int balance_;
public BankAccount(int initialBalance) {
if (initialBalance < 300) {
throw new IllegalArgumentException("Balance needs to be at least 300");
}
balance_ = initialBalance;
}
public void deposit(int amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Deposit has to be positive");
}
// should be atomic assignment
// copy should also be non-shared as it's on each thread's stack
int copy = balance_;
// do the work on the thread-local copy of the balance. This work should
// not be visible to other threads till below synchronization
copy += amount;
synchronized(this) {
balance_ = copy; // make the new balance visible to other threads
}
}
public void withdraw(int amount) {
// should be atomic assignment
// copy should also be non-shared as it's on each thread's stack
int copy = balance_;
if (amount > copy) {
throw new IllegalArgumentException("Withdrawal has to be <= current balance");
}
copy -= amount;
synchronized (this) {
balance_ = copy; // update the balance and make it visible to other threads.
}
}
public synchronized getBalance() {
return balance_;
}
}
请忽略balance_应为double而不是整数的事实。我知道原始类型的读取/赋值是原子的,除了双精度和长整数之外所以我选择了简单的整数
我试图在函数内部写评论来描述我的想法。编写此类是为了获得正确的同步以及最小化锁定下的代码量。这是我的问题:
答案 0 :(得分:2)
任何不在synchronized块内的代码都可以由多个线程统一执行,您的解决方案是在同步块之外创建新的余额,这样它就无法正常工作。让我们看一个例子:
int copy = balance_; // 1
copy += amount; //2
synchronized(this) {
balance_ = copy; // 3
}
最后,BankAccount有20个,但应该是35
这是正确的方法:
public class BankAccount {
private int balance_;
public BankAccount(int initialBalance) {
if (initialBalance < 300) {
throw new IllegalArgumentException("Balance needs to be at least 300");
}
balance_ = initialBalance;
}
public void deposit(int amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Deposit has to be positive");
}
synchronized(this) {
balance_ += amount;
}
}
public void withdraw(int amount) {
synchronized (this) {
if (amount > balance_) {
throw new IllegalArgumentException("Withdrawal has to be <= current balance");
}
balance_ -= amount;
}
}
public synchronized int getBalance() {
return balance_;
}
}
答案 1 :(得分:1)
此代码容易出现竞争条件。
考虑这一部分:
int copy = balance_;
copy += amount;
// here!
synchronized(this) {
balance_ = copy; // make the new balance visible to other threads
}
如果有人在“此处”部分调用withdraw
或deposit
会怎样?第二种方法会更改_balance
,但该更改不会反映在您的本地copy
中。然后,当您将copy
写入共享变量时,它将只覆盖该值。
处理此问题的方法是在独占锁下执行整个操作 - 读取,修改和写入。或者,您可以使用AtomicInteger,它提供原子incrementAndGet
方法。这通常可以编译为称为"compare and swap"的硬件原语,因此非常有效。缺点是它只为那一个操作提供原子性;如果你还需要其他一些操作也是原子的(也许你还想增加一个depositCounts
字段?),那么AtomicInteger将不起作用。