All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fj.control.parallel.Actor Maven / Gradle / Ivy

Go to download

Functional Java is an open source library that supports closures for the Java programming language

There is a newer version: 5.0
Show newest version
package fj.control.parallel;

import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;

import fj.Effect;
import fj.F;
import fj.Unit;
import fj.P1;
import fj.function.Effect1;

/**
 * Light weight actors for Java. Concurrency is controlled by a parallel Strategy.
 * The Strategy serves as the Actor's execution engine, and as its mailbox.
 * 

* Given some effect, the Actor performs the effect on its messages using its Strategy, transforming them * into instances of fj.P1. The P1 represents a possibly running computation which is executing the effect. *

* NOTE: A value of this type may generally process more than one message at a time, depending on its Strategy. * An actor is not thread-safe unless either its Effect imposes an order on incoming messages or its Strategy is * single-threaded. * * A queue actor which imposes an order on its messages is provided by the {@link #queueActor} static method. */ public final class Actor { private final Strategy s; private final F> f; /** * An Actor equipped with a queue and which is guaranteed to process one message at a time. * With respect to an enqueueing actor or thread, this actor will process messages in the same order * as they are sent. */ public static Actor queueActor(final Strategy s, final Effect1 ea) { return actor(Strategy.seqStrategy(), new Effect1() { // Lock to ensure the actor only acts on one message at a time final AtomicBoolean suspended = new AtomicBoolean(true); // Queue to hold pending messages final ConcurrentLinkedQueue mbox = new ConcurrentLinkedQueue<>(); // Product so the actor can use its strategy (to act on messages in other threads, // to handle exceptions, etc.) final P1 processor = new P1() { @Override public Unit _1() { // get next item from queue T a = mbox.poll(); // if there is one, process it if (a != null) { ea.f(a); // try again, in case there are more messages s.par(this); } else { // clear the lock suspended.set(true); // work again, in case someone else queued up a message while we were holding the lock work(); } return Unit.unit(); } }; // Effect's body -- queues up a message and tries to unsuspend the actor @Override public void f(T a) { mbox.offer(a); work(); } // If there are pending messages, use the strategy to run the processor void work() { if (!mbox.isEmpty() && suspended.compareAndSet(true, false)) { s.par(processor); } } }); } private Actor(final Strategy s, final F> e) { this.s = s; f = a -> s.par(e.f(a)); } /** * Creates a new Actor that uses the given parallelization strategy and has the given side-effect. * * @param s The parallelization strategy to use for the new Actor. * @param e The side-effect to apply to messages passed to the Actor. * @return A new actor that uses the given parallelization strategy and has the given side-effect. */ public static Actor actor(final Strategy s, final Effect1 e) { return new Actor<>(s, P1.curry(Effect.f(e))); } /** * Creates a new Actor that uses the given parallelization strategy and has the given side-effect. * * @param s The parallelization strategy to use for the new Actor. * @param e The function projection of a side-effect to apply to messages passed to the Actor. * @return A new actor that uses the given parallelization strategy and has the given side-effect. */ public static Actor actor(final Strategy s, final F> e) { return new Actor<>(s, e); } /** * Pass a message to this actor, applying its side-effect to the message. The side-effect is applied in a concurrent * computation, resulting in a product referencing that computation. * * @param a The message to send to this actor. * @return A unit-product that represents the action running concurrently. */ public P1 act(final A a) { return f.f(a); } /** * Contravariant functor pattern. Creates a new actor whose message is transformed by the given function * before being passed to this actor. * * @param f The function to use for the transformation * @return A new actor which passes its messages through the given function, to this actor. */ public Actor contramap(final F f) { return actor(s, (B b) -> act(f.f(b))); } /** * Transforms this actor to an actor on promises. * * @return A new actor, equivalent to this actor, that acts on promises. */ public Actor> promise() { return actor(s, (Promise b) -> b.to(Actor.this)); } }