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: Java 用の STM について知りました。パフォーマンスについてはわかりませんが、私が望んでいたものを提供しているようです。http://multiverse.codehaus.org/60second.html

4

4 に答える 4

4

より機能的な方法で、共有され同期された状態変数にアプローチする方法はたくさんあります。

トランザクション変数

ここでの古典的なアプローチは、トランザクションメモリを使用することです。

プログラムには正確に1つの共有状態変数があり、競合する書き込みのロールバックをサポートしています。Haskellでは、これはTVar(トランザクション変数)を介して、STMモナド(トランザクション変数を介してのみ状態をサポートするモナド)で表されます。

ここでSTMを使用する利点は、デッドロックの回避を保証できることです(ただし、ライブロックは引き続き可能です)。

メモリ変数

sなどの従来のアプローチを使用することもできますMVar。これらは、ロックとして動作する可変変数です。

  • 値は1つだけです
  • その値は削除するか、変数に入れることができます
  • スレッドが完全な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 に答える