2

我的问题是这个问题的更具体的实例:函数式编程:状态与重新分配

我是 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:刚刚发现了一个 STM for Java。不确定它的性能,但它似乎提供了我想要的。http://multiverse.codehaus.org/60second.html

4

4 回答 4

4

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

交易变量

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

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

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

内存变量

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

  • 它们只包含一个值
  • 该值可能会被删除或放入变量中
  • 如果一个线程试图写入一个完整的 MVar,它会阻塞
  • 如果一个线程试图从一个空的 MVar 中读取,它会阻塞

通过这种方式,您可以支持线程以原子方式更新共享值。

我会选择 STM 解决方案,因为这是最惯用的。

于 2012-05-01T12:02:05.970 回答
3

将其转变为功能性方法的主要目的是计算一个新世界。在您的示例中,银行状态就是您的世界,因此您希望为每个 TX 计算一个新的银行状态。这可能看起来像:

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(Function))。

出于说明目的,这是一个幼稚的实现,真正的实现将包含许多优化。

我使用的选项在这里:https ://bitbucket.org/atlassian/fugue/src/master/src/main/java/com/atlassian/fugue/Option.java

于 2012-05-01T23:28:01.960 回答
2

FP 是线程安全的,因为没有可变状态。现在您的示例包含可变状态。

紧随其后的是,您无法通过应用 FP 原则使其成为线程安全的,除非您找到一种方法来实现您想要的而不使用可变状态。

您可以有多个线程,每个帐户的余额为 0,并处理多个事务,从而保持已处理事务的总体效果。最后,您可以总结所有帐户和初始金额,并获得总体结果。

于 2012-05-01T08:59:17.553 回答
1

FP 的想法是避免可变状态,当状态必不可少时,您可以使用函数构造来模拟可变性。例如,在 Haskell 中,您可以使用monads来做到这一点。

于 2012-05-01T11:17:41.507 回答