【问题标题】:Java: state sharing between threads in functional programmingJava:函数式编程中线程之间的状态共享
【发布时间】:2012-05-10 18:26:24
【问题描述】:

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

【问题讨论】:

  • “我是 FP 的新手,并试图通过 Java 来理解它”这不是一个好主意。试试这个book.realworldhaskell.org
  • 我看不出线程安全和函数式编程之间的联系。您是否声称使用 FP 会否定同步的需要以实现线程安全?
  • @David Harkness 是的。使用 FP 的原因之一是,由于使用不可变状态的范例,不需要同步对状态的访问,因此 FP 本质上是线程安全且高效的(因为不涉及同步成本);因此特别推荐用于多核 cpu 的编程。会找到并引用参考文献。
  • @David Harknes pl。请参阅本文的“并发”部分:defmacro.org/ramblings/fp.html
  • @shrini1000 - 使用不可变对象使其成为线程安全的。您也可以使用非 FP 代码执行此操作。但是,您的示例有一个共享的 Bank 实例,其中包含可变数据。

标签: java functional-programming mutable


【解决方案1】:

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

事务变量

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

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

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

内存变量

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

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

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

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

【讨论】:

    【解决方案2】:

    将其转化为函数式方法的主要目的是计算一个新世界。在您的示例中,银行状态就是您的世界,因此您希望为每个 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

    【讨论】:

      【解决方案3】:

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

      你不能通过应用 FP 原则让它成为线程安全的,除非你找到一种方法来实现你想要的而不使用可变状态。

      您可以有多个线程,开始时每个帐户的余额为 0,并处理多个事务,从而保持已处理事务的聚合效果。最后,您可以将所有计数和初始金额相加,得出总体结果。

      【讨论】:

      • 谢谢。是不是说FP只适用于某类问题,不能直接适用于包含可变状态的问题?
      • 不完全。大多数(全部?)问题都可以重新表述以适应功能范式。因为重新表述的问题在内存和计算时间方面的成本可能要高得多,所以每种 FP 语言都有一些方法来改变状态,包括数组,以避免需要。
      【解决方案4】:

      FP 的想法是避免可变状态,当状态必不可少时,您可以使用函数构造来模拟可变性。例如,在 Haskell 中,您可以使用 monads 来执行此操作。

      【讨论】:

      • 请注意:您实际上并不是在“模拟”状态——在使用状态单子时您确实有状态。默认情况下不允许使用状态 - 您必须将其打开。
      • @DonStewart - 我说的是“模拟可变性”。
      • 仅在某些情况下,您甚至可以模拟可变性。例如。如果您使用的是ST monad,那么您只是在封装真正的可变单元格。 haskell.org/ghc/docs/latest/html/libraries/base/…
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-01
      • 2019-03-29
      • 2013-10-12
      • 2011-07-08
      • 2015-09-08
      • 1970-01-01
      相关资源
      最近更新 更多