Java:函数式编程中线程之间的状态共享

时间:2012-05-01 05:55:45

标签: java functional-programming mutable

我的问题是这个问题的更具体的实例: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

4 个答案:

答案 0 :(得分:3)

有许多方法可以更实用的方式处理共享的同步状态变量。

交易变量

这里的经典方法是使用事务性内存:

程序中有一个一个共享状态变量,支持对冲突写入进行回滚。在Haskell中,这将通过TVar(事务变量)在STM monad(仅通过事务变量支持状态的monad)中表示。

在这里使用STM的好处是你可以保证避免死锁(虽然仍然可以使用活锁)。

内存变量

您还可以使用更传统的方法,例如MVar。这些是可变的变量,表现为锁:

  • 它们只包含一个值
  • 该值可以删除或放入变量
  • 如果一个线程试图写入一个完整的MVar,它会阻止
  • 如果一个线程试图从一个空的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执行此操作。