JActor API 基础

原文链接:
https://www.ibm.com/developerworks/mydeveloperworks/blogs/jactor/entry/jactor_api_basics17?lang=en

在这里,我们将介绍一些JActor基本的API ,在例子里你会看到这些API非常容易使用。

JLPCActor

所有Actor都要继承JLPCActor类。actor的方法不必是线程安全的,因为他们总是被适当的线程调用。

Actor通过发送请求(request)来交互。但是,在发送和接收任何请求之前actor必须分配一个mailbox来管理它的输入输出列队。

Actor的方法分为异步和同步两种。异步方法可以使用异常处理程序,给其他actor发送消息(同步消息不行)。

异步方法必须有一个类型为RP的参数,用来作为返回其执行结果的回调对象。而方法本身的返回类型总是void。

使用同一个mailbox的actor可以互相调用同步方法,这是因为他们总是共享一个线程。

使用initialize(mailbox)方法可以给actor分配一个mailbox。使用initialize(mailbox, actor)方法可以在给actor分配一个mailbox的同时附加注入一个依赖(parent actor)。被注入依赖的actor递归的继承作为依赖被注入的actor(parent actor)的处理能力。所以当一个类型不合适的请求发送给一个actor时,他实际上被发送到这个actor的合适的依赖那里去了。

getMailbox() 方法返回分配给actor的mailbox,getMailboxFactory()方法返回创建这个mailbox的factory。

getParent() method返回被注入的依赖(parent actor),如果没有注入则返回null。getAncestor(actorClass)方法返回继承了参数actorClass的依赖(递归),如果没有则返回null。

Mailbox

mailbox有一个存放接收到消息(请求和响应)的inbox,和存放要发送消息的outbox。(在发送message之前将要发送的消息组成message blocks可以增加mailbox的吞吐量。)

当一个消息发送给一个有空的inbox的mailbox时,mailbox会从一个线程池中得到一个线程任务。

大部分mailbox也支持抢占(一个actor的线程直接发送消息来处理消息)。但是只能在目标actor的mailbox还没有分配线程任务的时候使用。

通常,消息通常在inbox里的最后一个消息被处理之后才会发送,但是调用sendPendingMessages()会强迫outbox里的所有消息被发送。

setInitialBufferCapacity(int)用来设置outbox的初始数量,否则默认为是10.(outbox被实现为数组列表)。

isEmpty()方法用来判断inbox是否还持有额外的需要处理的消息,如果没有返回true。

getMailboxFactory用于返回创建mailbox的factory。

JAMailboxFactory

JAMailboxFactory负责管理线程池和创建mailbox。它有一个带int类型参数的构造函数,该参数指定线程池中的线程数量。

close()关闭线程池。

createMailbox()返回一个可以被抢占的mailbox。

createAsyncMailbox()返回一个不能被抢占的mailbox。This is used to force message processing of all the actors assigned that mailbox to process the message on a thread that is not the same as the thread used by the actor which sent the message–providing the source and target actors have not been assigned the same mailbox.

addClosable(Closable)和removeClosable(Closable)会想一个列表添加或移除Closable对象。当close()方法被调用时,这个列表里的所有Closable对象的close方法都会被调用。

第一次调用timer()方法是,会创建一个java.util.Timer对象。以后每次timer()都会返回这个Timer对象。并且当调用close()时,会转而调用这个对象的cancel()方法。

Request

在两个actor之间传送的qequest必须继承Request类,这个类有两个参数:RESPONSE_TYPE,当request被成功处理时的结果class类型;TARGET_TYPE,目标actor的class类型。

isTargetType(targetActor)是一个抽象方法,子类的实现应该在targetActor是TARGET_TYPE的一个实例时返回true。

processRequest(targetActor, rp)是一个抽象方法,通常子类的实现要调用一个targetActor的方法。对于actor的异步方法而言,参数rp仅仅是简单的传递给该方法,但是对于actor同步方法而言,必须调用rp.processResult(result)方法,result作为该同步方法的返回值。(如果一个actor的同步方法返回类型为void,那么还是不许返回一个null)。

(注意processRequest总是被正确的线程调用一遍保持目标actor的线程安全。Typically this is the same thread being used by the actor which is sending the message, but not always.)

send(sourceActor, targetActor, rp) 方法用于从一个actor发送一个2-way请求消息到另一个actor,rp参数用来回调处理返回值。

send(jaFuture, targetActor)方法用于从非actor代码发送一个2-way请求消息到一个actor。这个方法在返回结构之前会一直等待。

sendEvent(sourceActor, targetActor) 方法用于从一个actor发送一个1-way请求消息到另一个actor。

sendEvent(targetActor)方法用于从非actor代码发送一个1-way请求消息到一个actor。

Getting Started

从这里下载Jactor-4.5.0.zip(版本会改变)。然后提取jactor-4.5.0.jar文件并复制到GettingStarted路径下。你还需要slf4j的jar文件。

在GettingStarted路径下创建文件j.bat:

<!-- lang: shell -->
del *.class
call javac -classpath jactor-4.5.0.jar;slf4j-api-1.7.2.jar *.java
call java -classpath .;jactor-4.5.0.jar;slf4j-api-1.7.2.jar;slf4j-simple-1.7.2.jar GettingStarted

j.bat文件能被用来编译和运行测试。

给actor发送消息

这是创建测试actor和给他发送开始请求的main方法。


import org.agilewiki.jactor.*;

public class GettingStarted {
    public static void main(String[] args) throws Exception {

        // Create a mailbox factory with a pool of 10 threads.
        MailboxFactory mailboxFactory = 
            JAMailboxFactory.newMailboxFactory(10);

        // Create and initialize a Test actor.
        Mailbox mailbox = mailboxFactory.createMailbox();
        Test test = new Test();
        test.initialize(mailbox);

        // Send a Start request and wait for completion.
        JAFuture future = new JAFuture();
        Start.req.send(future, test);

        // Shut down the thread pool.
        mailboxFactory.close();
    }
}

The Start request is a singleton, as it has no parameters and is impermiable. The result returned is always null, so the RESPONSE_TYPE is Object. And the TARGET_TYPE is Test. The Start request calls the synchronous method Test.start().


import org.agilewiki.jactor.*;
import org.agilewiki.jactor.lpc.*;

public class Start extends Request<Object, Test> {
    public static final Start req = new Start();

    public void processRequest(JLPCActor targetActor, RP rp) 
            throws Exception {
        Test a = (Test) targetActor;
        a.start();
        rp.processResponse(null);
    }
    public boolean isTargetType(Actor targetActor) {
        return targetActor instanceof Test;
    }
}

The Test actor sports a single synchronous method, start.


import org.agilewiki.jactor.lpc.*;

public class Test extends JLPCActor {
    public void start() throws Exception {
        System.out.println("Hello world!");
    }
}

Output

Hello world!

Calling a Synchronous Method on Another Actor

There are times when one actor can directly call the synchronous methods of another actor. For example, when initializing the other actor or when both actors use the same mailbox.

Below we have modified the Test actor to create a Greeter actor which shares the same mailbox and then print the greeting returned by the Greeter.greet() method.


import org.agilewiki.jactor.lpc.*;

public class Test extends JLPCActor {
    public void start() throws Exception {
        Greeter greeter = new Greeter();
        greeter.initialize(getMailbox());
        String greeting = greeter.greet();
        System.out.println(greeting);
    }
}

The Greeter actor has only a single synchronous method, greet.


import org.agilewiki.jactor.lpc.*;

public class Greeter extends JLPCActor {
    public String greet() throws Exception {
        return "Hello world!";
    }
}

Output

Hello world!

Sending a Request to Another Actor

Instead of the Test and Greeter actors using the same mailbox, we will have them use different mailboxes, and have the Test actor send a Greet request to the Greeter actor. But as only asynchronous methods can send messages, we will need to change the parmeters of Test.start, which means changing the Start request as well.

The change to the Start request is minor–we only need to change the processRequest method.


import org.agilewiki.jactor.*;
import org.agilewiki.jactor.lpc.*;

public class Start extends Request<Object, Test> {
    public static final Start req = new Start();

    public void processRequest(JLPCActor targetActor, RP rp) 
            throws Exception {
        Test a = (Test) targetActor;
        a.start(rp);
    }

    public boolean isTargetType(Actor targetActor) {
        return targetActor instanceof Test;
    }
}

Changes to the Test actor are a bit more interesting. In addition to creating another mailbox for initializing the Greeter actor, we must use a callback to receive the value returned by the Greet request.


import org.agilewiki.jactor.*;
import org.agilewiki.jactor.lpc.*;

public class Test extends JLPCActor {
    public void start(final RP rp) throws Exception {
        Greeter greeter = new Greeter();
        greeter.initialize(getMailboxFactory().createMailbox());
        Greet.req.send(this, greeter, new RP<String>() {
            public void processResponse(String greeting)
                    throws Exception {
                System.out.println(greeting);
                rp.processResponse(null);
            }
        });
    }
}

We also need to define the Greet request, a singleton which calls the synchronous method Greeter.greet() and then sends back the returned value.


import org.agilewiki.jactor.*;
import org.agilewiki.jactor.lpc.*;

public class Greet extends Request<String, Greeter> {
    public static final Greet req = new Greet();

    public void processRequest(JLPCActor targetActor, RP rp) 
            throws Exception {
        Greeter a = (Greeter) targetActor;
        rp.processResponse(a.greet());
    }

    public boolean isTargetType(Actor targetActor) {
        return targetActor instanceof Greeter;
    }
}

Output

Hello world!

Composing Actors with Dependency Injection

Dependency injection can be used to compose cactus stacks of actors, with actors able to send messages that will be processed by the appropriate ancestor. (In a cactus stack the links point towards the root, which is the reverse of a tree.)

We will define a simple Printer actor which has a Print request, and then inject this actor into a modified Greeter actor which prints its own greeting.


import org.agilewiki.jactor.*;
import org.agilewiki.jactor.lpc.*;

public class Test extends JLPCActor {
    public void start(final RP rp) throws Exception {
        Printer printer = new Printer();
        printer.initialize(getMailbox());
        final Greeter greeter = new Greeter();
        greeter.initialize(getMailbox(), printer);
        (new Print("Greeting:")).
                send(this, greeter, new RP() {
                    public void processResponse(Object rsp) 
                            throws Exception {
                        Greet.req.send(Test.this, greeter, rp);
                    }
        });
    }
}

Test now creates and initializes a Printer actor, and then creates a Greeter actor initialized with the Printer actor as its parent. A Print request is sent to the Greeter actor and, on completion, a Greet request is sent.


import org.agilewiki.jactor.*;
import org.agilewiki.jactor.lpc.*;

public class Greeter extends JLPCActor {
    public void greet(RP rp) throws Exception {
        (new Print("Hello world!")).send(this, this, rp);
    }
}

The Greeter actor now sends a Print request to itself, passing the rp parameter on so that on completion of the Print request the Greet request also completes.


import org.agilewiki.jactor.*;
import org.agilewiki.jactor.lpc.*;

public class Printer extends JLPCActor {
    public void print(String value) throws Exception {
        System.out.println(value);
    }
}

The Printer actor is quite simple, having a synchronous method which prints the value that is passed.


import org.agilewiki.jactor.*;
import org.agilewiki.jactor.lpc.*;

public class Print extends Request<Object, Printer> {
    private final String value;

    public Print(String value) {
        this.value = value;
    }

    public void processRequest(JLPCActor targetActor, RP rp) 
            throws Exception {
        Printer a = (Printer) targetActor;
        a.print(value);
        rp.processResponse(null);
    }

    public boolean isTargetType(Actor targetActor) {
        return targetActor instanceof Printer;
    }
}

Print is the first request that we have looked at which is not a singleton. This is because the Print request must hold the value to be printed.

Output

Greeting:
Hello world!

Forcing Parallel Operations

Whenever possible, messages are passed and processed on the same thread. This achieves the highest performance most of the time. But if you need to have an actor which operates on a separate thread, you need only assign it an asynchronous mailbox.


import org.agilewiki.jactor.*;
import org.agilewiki.jactor.lpc.*;

public class Test extends JLPCActor {
    public void start(final RP rp) throws Exception {
        Timer timer1 = new Timer();
        timer1.initialize(
            getMailboxFactory().createAsynchronousMailbox());
        Timer timer2 = new Timer();
        timer2.initialize(
            getMailboxFactory().createAsynchronousMailbox());
        final long t0 = System.currentTimeMillis();

        RP prp = new RP() {
            boolean pending = true;

            public void processResponse(Object obj) throws Exception {
                if (pending) pending = false;
                else {
                    System.out.println(System.currentTimeMillis()-t0);
                    rp.processResponse(null);
                }
            }
        };

        (new Delay(1000)).send(this, timer1, prp);
        (new Delay(1000)).send(this, timer2, prp);
    }
}

The modified Test actor above creates two Timer actors, initializes each of them with its own asynchronous mailbox. And then sends a Delay request to each of them, but with the same callback, prp.

The prp callback receives the responses from the two Timer actors and, on receipt of the second response, prints the elapsed time and sends back a null response to the Start request.


import org.agilewiki.jactor.*;
import org.agilewiki.jactor.lpc.*;

public class Delay extends Request<Object, Timer> {
    public final int ms;
    public Delay(int ms) {
        this.ms = ms;
    }

    public void processRequest(JLPCActor targetActor, RP rp) 
            throws Exception {
        Timer a = (Timer) targetActor;
        a.delay(ms);
        rp.processResponse(null);
    }

    public boolean isTargetType(Actor targetActor) {
        return targetActor instanceof Timer;
    }
}

The Delay request calls the synchronous Timer.delay(ms) method.


import org.agilewiki.jactor.*;
import org.agilewiki.jactor.lpc.*;

public class Timer extends JLPCActor {
    public void delay(int ms) throws Exception {
        Thread.sleep(ms);
    }
}

The Timer actor simply blocks the thread it is assigned to for a number of milliseconds.

Output

1003

The total elapsed time to run two timers in parallel for 1000 milliseconds each was 1003 milliseconds. And the reason for the additional 3 milliseconds is due to the inherent latency of passing messages between threads.

Continuations

When a request is received, it comes with an RP continuation. Calling the processResponse(rsp) method returns the response to the actor that sent the request in a thread safe way.

Continuations can be saved and a response sent at a later time. The only restriction here is that when sending a response it must be done within the context of the actor that received the request. (Or another actor with the same mailbox.)


import org.agilewiki.jactor.*;
import org.agilewiki.jactor.lpc.*;

public class Test extends JLPCActor {
    public void start(final RP rp) throws Exception {
        Greeter greeter = new Greeter();
        greeter.initialize(getMailbox());
        Greet.req.send(this, greeter, new RP<String>() {
            public void processResponse(String greeting)
                    throws Exception {
                System.out.println(greeting);
                rp.processResponse(null);
        });
        System.out.println("trigger...");
        Trigger.req.sendEvent(this, greeter);
    }
}

The Test actor creates and initializes the Greeter actor, sends a Greet request, prints “trigger…“, sends a Trigger event, and then, on receipt of the response to the Greet request, it prints the greeting.


import org.agilewiki.jactor.*;
import org.agilewiki.jactor.lpc.*;

public class Trigger extends Request<Object, Greeter> {
    public static final Trigger req = new Trigger();

    public void processRequest(JLPCActor targetActor, RP rp) 
            throws Exception {
        Greeter a = (Greeter) targetActor;
        a.trigger();
        rp.processResponse(null);
    }

    public boolean isTargetType(Actor targetActor) {
        return targetActor instanceof Greeter;
    }
}

The Trigger request simply calls the synchronous method Greeter.trigger(). There is nothing special about this request to make it an event request. Only it is used as an event by the Test actor which does a Trigger.sendEvent rather than a Trigger.send.


import org.agilewiki.jactor.*;
import org.agilewiki.jactor.lpc.*;

public class Greeter extends JLPCActor {
    private RP rp;

    public void greet(RP rp) throws Exception {
        this.rp = rp;
    }

    public void trigger() throws Exception {
        rp.processResponse("Hello world!");
    }
}

The Greeter class saves the continuation on receipt of a Greet request. And on receipt of a Trigger request it uses that saved continuation to send back the requested greeting.

Output

Trigger…
Hello world!

你可能感兴趣的:(jactor)