我的问题是这个问题的更具体的实例:Functional programming: state vs. reassignment
我是FP的新手,并试图通过Java来理解它。
我有以下类,其对象在多个线程之间共享:
public class Bank
{
private double[] accounts = new double[1000];
public synchronized void transfer(int from, int to, double amount)
{
account[from] -= amount;
account[to] += amount;
}
}
(这是一个非常简化的例子,因此省略了其他细节,例如验证和条件等待。)
因为'transfer'方法是同步的,所以即使它与多个线程共享,Bank对象的可变状态也不会被破坏。如果我想通过Java中的FP实现相同的功能,我该如何编写该代码? (我想看一个实际的代码示例)。
编辑:我对FP的兴趣源于其编写线程安全的并发应用程序的潜力。以下是指向该文章的文章的链接:http://www.defmacro.org/ramblings/fp.html
EDIT2:刚刚发现了一个关于Java的STM。不确定它的性能,但它似乎提供了我想要的。 http://multiverse.codehaus.org/60second.html
答案 0 :(得分:3)
有许多方法可以更实用的方式处理共享的同步状态变量。
交易变量
这里的经典方法是使用事务性内存:
程序中有一个一个共享状态变量,支持对冲突写入进行回滚。在Haskell中,这将通过TVar
(事务变量)在STM
monad(仅通过事务变量支持状态的monad)中表示。
在这里使用STM的好处是你可以保证避免死锁(虽然仍然可以使用活锁)。
内存变量
您还可以使用更传统的方法,例如MVar
。这些是可变的变量,表现为锁:
通过这种方式,您可以支持线程以原子方式更新共享值。
我会选择STM解决方案,因为那是最惯用的。
答案 1 :(得分:2)
FP是线程安全的,因为没有可变状态。 现在你的例子包含可变状态。
除非你找到一种方法来实现你想要的东西而不使用可变状态,否则你无法通过应用FP原则使其保持线程安全。
您可以拥有多个线程,每个账户的余额为0,并处理多个交易,从而保持已处理交易的总体效果。最后,您可以总结所有的金额和初始金额,并获得总体结果。
答案 2 :(得分:2)
将其转变为功能性方法的主要方法是计算一个新的世界。在您的示例中,Bank状态是您的世界,因此您希望为每个TX计算新的Bank状态。这可能类似于:
class BankState implements Function<Id, AccountState> {
final Map<Id, AccountState> balances; // ctor injected immutable
/** initial ctor, build a map of balances computed by from function */
BankState(Function<Id, Option<AccountState>> from, Iterable<Id> accounts) {
this.balances = computeMap(from, accounts);//
}
/** overlay ctor, if account state provided by the tx use that,
* otherwise the old one is used */
BankState(Function<Id, Option<AccountState>> tx, Map<Id, AccountState> old) {
this.balances = overlay(tx, old);// special map with overlay
}
public AccountState apply(Id id) {return balances.get(id);}
public BankState update(Function<Id, Option<AccountState>> tx) {
return new BankState(tx, balances);
}
public BankState transfer(final Id from, final Id to, final Money amount) {
return update(new Function<Id, Option<AccountState>>() {
public Option<AccountState> apply(Id id) {
if (id.equals(from) return some(bank.apply(id).debit(amount));
if (id.equals(to) return some(bank.apply(id).credit(amount));
return none();
}
});
}
}
然后,您可以简单地使用AtomicReference来保存当前状态并以原子方式将其更新为新的BankState。
你需要覆盖和计算地图实现,但这些很容易使用Guava创建(例如它已经有MapMaker.makeComputingMap(函数))。
这是一个用于说明目的的天真实现,真正的实现将包含许多优化。
我使用的选项在这里:https://bitbucket.org/atlassian/fugue/src/master/src/main/java/com/atlassian/fugue/Option.java
答案 3 :(得分:1)
FP的想法是你避免可变状态,当状态是必不可少的时候,你使用功能结构来模拟可变性。例如,在Haskell中,您可以使用monads执行此操作。