2012-05-01 3 views
1

Мой вопрос более конкретно конкретизация этого вопроса: Functional programming: state vs. reassignmentJava: состояние обмен между потоками в функциональном программировании

Я Newbee к 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; 
    } 
} 

(Это очень упрощенный пример, следовательно, и другие детали, такие как проверка и состояние ожидания опущены).

Поскольку метод «передачи» синхронизирован, измененное состояние объекта банка не будет повреждено, даже если оно разделено несколькими потоками. Если я хочу добиться того же, что и в FP на Java, как бы написать этот код? (Я хотел бы увидеть фактический пример кода).

EDIT: мой интерес к FP проистекает из его потенциала для написания потокобезопасных одновременных приложений. Ниже приведена ссылка на статью, которая утверждает это: http://www.defmacro.org/ramblings/fp.html

EDIT2: только что узнал о STM для Java. Не уверен в его производительности, но он, кажется, обеспечивает то, что я хотел. http://multiverse.codehaus.org/60second.html

+4

«Я Newbee к FP и пытаюсь понять его через Java» Это не очень хорошая идея. Попробуйте это вместо http://book.realworldhaskell.org/ –

+0

Я не вижу связи между безопасностью потоков и функциональным программированием. Вы утверждаете, что использование FP приведет к отказу от необходимости синхронизации для обеспечения безопасности потоков? –

+0

@ Давид Харкнесс Да. Одна из причин, по которым используется FP, заключается в том, что из-за парадигмы, использующей неизменяемое состояние, нет необходимости синхронизировать доступ к состоянию, следовательно, FP по своей сути является потокобезопасным и эффективным (поскольку не задействована стоимость синхронизации); и поэтому рекомендуется специально для программирования для многоядерных процессоров. Найду и приведу ссылку. – shrini1000

ответ

3

Существует много способов приблизиться к вашей общей синхронизированной переменной состояния более функциональным способом.

транзакционные переменные

Классический подход здесь заключается в использовании транзакционной памяти:

у вас есть точно один общих переменные состояния в программе, поддерживая откат на конфликтующих записях. В Haskell это будет представлено через TVar (переменная транзакции), в монаде STM (монада, которая поддерживает состояние только через транзакционные переменные).

Преимущество использования STM здесь в том, что вы можете гарантировать предотвращение взаимоблокировки (хотя доступ к жилью по-прежнему возможен).

Переменные памяти

Вы можете также использовать более традиционные подходы, такие как MVar с. Это изменяемые переменные, которые ведут себя как замки:

  • они содержат только одно значение
  • , что значение может быть удалено или поместить в переменную
  • если поток пытается записать в полной MVar, он блокирует
  • если поток пытается читать из пустого MVar, он блокирует

Таким образом, вы можете поддерживать темы, обновляя общую стоимость атомарно.

Я бы пошел на решение STM, так как это самый идиоматический.

2

FP является потокобезопасным, потому что нет изменчивого состояния. Теперь ваш пример содержит изменяемое состояние.

Это не означает, что вы не можете сделать его потокобезопасным, применяя принципы FP, если только не найдете способ добиться того, чего хотите, без использования изменяемого состояния.

У вас может быть несколько потоков, которые начинаются с баланса 0 для каждой учетной записи и обрабатывают несколько транзакций, тем самым сохраняя совокупный эффект обработанных транзакций. В конце вы можете суммировать все счета и начальные суммы и получить общий результат.

+0

Thx. Означает ли это, что FP применим только к определенному классу проблем и не будет непосредственно применяться к проблемам, содержащим изменчивое состояние? – shrini1000

+0

Не совсем. Большинство (все?) Проблемы могут быть переформулированы так, чтобы соответствовать функциональной парадигме. Поскольку переформулированная проблема может быть намного более дорогостоящей с точки зрения памяти и вычислительного времени, на каждом языке FP есть несколько способов изменить состояние, включая массивы, чтобы избежать необходимости. – Ingo

1

Идея FP заключается в том, что вы избегаете изменчивого состояния, а когда состояние важно, вы имитируете изменчивость с использованием функциональных конструкций. Например, в Haskell вы можете сделать это, используя monads.

+0

+1 за предложение монады. – shrini1000

+0

Просто примечание: вы на самом деле не «имитируете» состояние - у вас действительно есть состояние при использовании государственной монады. Состояние по умолчанию не разрешено - вам нужно включить его. –

+0

@DonStewart - Я сказал «имитирующую изменчивость». –

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