package com.zqh.actor; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; /** * https://github.com/cloudjun/SimpleActorJava */ class ActorStatus { public static final int AVAILABLE = 0; public static final int EXECUTING = 1; public static final int ENDED = 2; public ActorStatus() { status = new AtomicInteger(AVAILABLE); } private AtomicInteger status; public AtomicInteger getStatus() { return status; } } interface IActor { // queue size long messageCount(); // if the message queue is empty // boolean isEmpty(); // if someone has stopped the actor boolean isEnded(); // return the current status (defined in ActorStatus) of the actor AtomicInteger getStatus(); // the work that the actor needs to do void execute(); } public abstract class Actor<E> implements IActor{ // Actor can have its own way to queue messages. We choose ConcurrentLinkedQueue, it could also be a BlockingQueue. private ConcurrentLinkedQueue<E> queue; private ActorStatus status; private boolean ended; @Override public long messageCount() { return queue.size(); } public Actor() { queue = new ConcurrentLinkedQueue<>(); status = new ActorStatus(); ended = false; } // @Override // public boolean isEmpty() { // return queue.isEmpty(); // } @Override public boolean isEnded() { return ended; } public void stop() { ended = true; } @Override public AtomicInteger getStatus() { return status.getStatus(); } public abstract void doWork(E message); @Override public void execute() { E message = queue.poll(); if (message != null) { doWork(message); } } public void addMessage(E message) { queue.offer(message); GateKeeper.readyToExecute(this); } } class GateKeeper { static ExecutorService executorService = Executors.newFixedThreadPool(10); /** * make sure the actor status is correct, and run the actor in a thread pool thread * @param actor */ public static void readyToExecute(final IActor actor) { AtomicInteger atom = actor.getStatus(); if (actor.isEnded()) { atom.set(ActorStatus.ENDED); return; } // only available actor can do work if (atom.compareAndSet(ActorStatus.AVAILABLE, ActorStatus.EXECUTING)) { executorService.execute(() -> Execute(actor)); } } /** * make sure the actor status is correct after the execution, and check if it is available to run again. * @param actor */ static void Execute(IActor actor) { actor.execute(); AtomicInteger atom = actor.getStatus(); if (actor.isEnded()) { atom.set(ActorStatus.ENDED); return; } atom.set(ActorStatus.AVAILABLE); // Call readyToExecute() again is to // 1) ensure the fairness of all the actors for the thread pool // 2) make sure this model works for tons of actors sharing a small thread pool // We can make some changes here so it can reduce scheduling cost by going directly into the message queue // to pull the next message. if (actor.messageCount() > 0) { readyToExecute(actor); } } }