【发布时间】:2015-12-02 21:30:05
【问题描述】:
自从我阅读了Java Concurrency in Practice 这本书后,我想知道如何使用不变性来简化线程之间的同步问题。
我完全理解不可变对象是线程安全的。它的状态在初始化后不能改变,所以根本不可能有“共享可变状态”。但是不可变对象必须正确使用才能被认为在同步问题中有用。
以这段代码为例,它描述了一家拥有许多账户的银行,并公开了一种我们可以在账户之间转账的方法。
public class Bank {
public static final int NUMBER_OF_ACCOUNT = 100;
private double[] accounts = new double[NUMBER_OF_ACCOUNT];
private Lock lock;
private Condition sufficientFunds;
public Bank(double total) {
double singleAmount = total / 100D;
for (int i = 0; i < NUMBER_OF_ACCOUNT; i++) {
accounts[i] = singleAmount;
}
lock = new ReentrantLock();
sufficientFunds = lock.newCondition();
}
private double getAdditionalAmount(double amount) throws InterruptedException {
Thread.sleep(1000);
return amount * 0.04D;
}
public void transfer(int from, int to, double amount) {
try {
// Not synchronized operation
double additionalAmount = getAdditionalAmount(amount);
// Acquiring lock
lock.lock();
// Verifying condition
while (amount + additionalAmount > accounts[from]) {
sufficientFunds.await();
}
// Transferring funds
accounts[from] -= amount + additionalAmount;
accounts[to] += amount + additionalAmount;
// Signaling that something has changed
sufficientFunds.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public double getTotal() {
double total = 0.0D;
lock.lock();
try {
for (int i = 0; i < NUMBER_OF_ACCOUNT; i++) {
total += accounts[i];
}
} finally {
lock.unlock();
}
return total;
}
public static void main(String[] args) {
Bank bank = new Bank(100000D);
for (int i = 0; i < 1000; i++) {
new Thread(new TransferRunnable(bank)).start();
}
}
}
在上面的例子中,来自Core Java Volume I一书,它通过显式锁使用同步。该代码显然难以阅读且容易出错。
我们如何使用不变性来简化上面的代码?我试图创建一个不可变的Accounts 类来保存帐户值,给Bank 类一个volatile 的Accounts 实例。但是我还没有达到我的目标。
谁能解释我是否可以使用不变性来简化同步?
---编辑---
可能我没有很好地解释自己。我知道不可变对象一旦创建就无法更改其状态。而且我知道,对于在 Java Memory Model (JSR-133) 中实现的规则,可以保证在初始化后看到不可变对象完全构造(有一些 distingua)。
然后我尝试使用这些概念从Bank 类中删除显式同步。我开发了这个不可变的Accounts 类:
class Accounts {
private final List<Double> accounts;
public Accounts(List<Double> accounts) {
this.accounts = new CopyOnWriteArrayList<>(accounts);
}
public Accounts(Accounts accounts, int from, int to, double amount) {
this(accounts.getList());
this.accounts.set(from, -amount);
this.accounts.set(to, amount);
}
public double get(int account) {
return this.accounts.get(account);
}
private List<Double> getList() {
return this.accounts;
}
}
Bank 类的帐户属性必须使用volatile 变量发布:
private volatile Accounts accounts;
很明显,Bank类的传输方式会相应改变:
public void transfer(int from, int to, double amount) {
this.accounts = new Accounts(this.accounts, from, to, amount);
}
使用不可变对象 (Accounts) 来存储类的状态 (Bank) 应该是一种发布模式,这在 JCIP 一书的第 3.4.2 段中有所描述。
但是,在某处仍然存在竞态条件,我不知道在哪里(以及为什么!!!)。
【问题讨论】:
-
不可变对象永远不会被修改。正如连接两个字符串不会改变任何一个字符串,而是生成一个全新的字符串对象一样,不可变 Accounts 类中的
transfer方法需要创建并返回一个包含新值的全新 Accounts 对象。 -
不变性可能是您在处理线程安全时应该尝试的第一件事。然而,它并不总是最好的方法。对于像这个银行账户这样似乎需要可变性的东西,您必须使用锁或
synchronized关键字来获得正确的行为。不变性很好,但不是灵丹妙药。 -
@VGR 我已经编辑了我的问题以更好地关注我的需求:)
标签: java multithreading thread-safety immutability