
fj.control.parallel.Actor Maven / Gradle / Ivy
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));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy