fj.control.parallel.Actor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of functionaljava Show documentation
Show all versions of functionaljava Show documentation
Functional Java is an open source library that supports closures for the Java programming language
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
AtomicBoolean suspended = new AtomicBoolean(true);
// Queue to hold pending messages
ConcurrentLinkedQueue mbox = new ConcurrentLinkedQueue();
// Product so the actor can use its strategy (to act on messages in other threads,
// to handle exceptions, etc.)
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
protected 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));
}
}