This article was first published by Huang Wenhai in the Infoq Chinese station: Http://www.infoq.com/cn/articles/Java-multithreaded-programming-mode-active-object-part1. Reprint Please specify the author: Huang Wenhai Source:http://viscent.iteye.com.
schema for Active object mode When an asynchronous method exposed by an active object mode is invoked, the context information associated with the method invocation, including the invoked asynchronous method name (or the operation it represents), and the parameters passed by the caller code, are encapsulated into an object. This object is called a method request. The method request object is stored in the buffer (activation Queue) maintained by the active object mode, and is handled by the specialized worker thread according to the context information it contains. That is, the method request object is generated by a thread that runs the caller code by calling the active object mode externally exposed asynchronous method, and the action represented by the method request is executed by a specialized thread, thus enabling the separation of the invocation and execution of the method, resulting in concurrency.
The main participants in Active object mode are the following. Its class diagram is shown in Figure 2.
Figure 2. Class diagram of Active object mode
(click image to enlarge)
Proxy: is responsible for exposing the asynchronous method interface externally. When the caller code calls the asynchronous method DoSomething of the participant instance, the method generates a corresponding Methodrequest instance and stores it in the buffer maintained by scheduler. The return value of the DoSomething method is an outer wrapper object that represents the result of its execution: an instance of the future participant. The asynchronous method dosomething runs in the same thread as the caller code. methodrequest: Encapsulates the invocation of the caller code to an asynchronous method of the proxy instance as an object. This object retains contextual information such as the name of the asynchronous method and the parameters passed by the caller code. It makes it possible to detach the invocation and execution of a proxy's asynchronous method. Its call method invokes the corresponding method of the servant instance based on the context information it contains. activationqueue: The buffer that is responsible for temporarily storing the Methodrequest instance that was created when the asynchronous method of the proxy was invoked. Scheduler: is responsible for depositing the Methodrequest instance created by the proxy's asynchronous method into its maintained buffer. And according to a certain scheduling strategy, the Methodrequest instance in the buffer maintained by it is executed. Its scheduling strategy can be based on actual needs, such as FIFO, LIFO and according to the information contained in the Methodrequest priority. Servant: Responsible for the implementation of the asynchronous methods exposed by proxy. Future: is responsible for storing and returning the execution results of the active object asynchronous method.
The sequence diagram for Active object mode is shown in Figure 3.
Figure 3. Sequence diagram of Active object mode
(click image to enlarge)
Step 1th: The caller code invokes the asynchronous method DoSomething of the proxy.
Step 2nd to 7th: The DoSomething method creates a future instance as the return value of the method. and encapsulates the caller code's call to the method as a Methodrequest object. The scheduler Enqueue method is then invoked as a parameter by the created Methodrequest object to store the Methodrequest object in the buffer. The scheduler Enqueue method invokes the Enqueue method of the Activationqueue instance that scheduler maintains, and the Methodrequest object is saved to the buffer.
Step 8th: DoSomething Returns the future instance that it created.
Step 9th: The Scheduler instance uses a dedicated worker thread to run the dispatch method.
Step 10th to 12th: The dispatch method calls the Dequeue method of the Activationqueue instance to obtain a Methodrequest object. The call method of the Methodrequest object is then invoked
Step 13th to 16th: The call method of the Methodrequest object invokes the corresponding method dosomething of its associated servant instance. and sets the return value of the Servant.dosomething method to the future instance.
Step 17th: The Methodrequest object's call method returns.
In the previous steps, step 1th to 8th is running in the caller thread of active object, which enables you to encapsulate the caller code's invocation of the asynchronous method provided by active object as an object (method Request) and store it in a buffer. These steps enable the submission of the task. The 9th to 17th step is to run in the worker thread of active object, which implements reading and executing the method Request from the buffer, and executes the task. Thus, the invocation and execution of active object externally exposed asynchronous methods are separated.
If the caller code is concerned with the return value of an asynchronous method of active object, you can call the Get method of the future instance to obtain the true execution result of the asynchronous method, as needed. Active Object Mode combat Case
A telecommunication software has a MMS module. Its main function is to achieve mobile phone users to other mobile phone users to send MMS, the receiver number can be filled out as the other side of the cornet. For example, when a user 13612345678 sends a multimedia message to a colleague 13787654321, the receiver number can be filled out as the other's cornet, such as 776, rather than its real number.
One of the key operations of the module in processing its received MMS requests is to query the database to obtain the true number (trombone) of the receiver's cornet. The operation may fail because of a database failure, thus making the entire request unable to continue processing. and database failure is recoverable fault, so in the cornet conversion to the trombone in the process of database anomalies, you can first of all send MMS request message cache to disk, wait until the database restore, and then read the request message from the disk, to try again. For convenience, we can use the Java Object Serialization API to serialize an object that represents a MMS to a disk file to achieve the request cache. Here we discuss other factors that need to be considered for this request caching operation, and how the active object pattern can help us meet these considerations.
First, the request message is cached to a slow operation on the disk involving file I/O, and we do not want it to execute in the main thread of the request processing (that is, the worker thread of the Web server). Because of this, the response time of the module is increased and the response of the system is reduced. and makes the Web server's worker threads reduce system throughput by waiting for file I/O. At this point, asynchronous processing comes in handy. The Active object pattern can help us implement the commit and execution separation of the Request caching task: The task is committed on the Web server's worker thread, and the execution of the task, including the medium operation of the serialized object to disk file, is performed in the active object worker thread. In this way, the main thread of request processing can trigger the caching of the current MMS request when it detects the failure of the cornet trombone, and then continues its request processing, such as responding to the client. At this point, the current request message may be being cached to the file by the active object thread. As shown in Figure 4.
Figure 4. Asynchronous implementation Caching
Second, each cornet to the failure of the trombone message sent by the MMS request messages will be cached as a disk file. But we don't want these cached files to be in the same subdirectory. Instead, you want multiple cached files to be stored in more than one subdirectory. Each subdirectory can store up to a specified number of cached files, such as 2000. If the current subdirectory is full, create a new subdirectory to store the newly cached file until the subdirectory is also full, and so on. When the number of these subdirectories reaches a specified number (such as 100), the oldest subdirectory (along with its cached file, if any) is deleted. So that the number of subdirectories is also fixed. Obviously, in a concurrency environment, the implementation of this control requires some concurrent access control (such as control through locks), but we do not want this control to be exposed to other code that handles the request. Proxy participants in active object mode can help us encapsulate concurrent access control.
Below, we look at the code in the case that satisfies both of these goals by applying the active object pattern when implementing caching. First look at the entry class for the request processing. This class is the guest caller code of the active object mode in this case. As shown in Listing 2.
Listing 2. MMS issued request processing of the entry class
public class Mmsdeliveryservlet extends HttpServlet {private static final long serialversionuid = 5886933373599895099L; @Override public void DoPost (HttpServletRequest req, HttpServletResponse resp) throws Servletexception, IOException
{///resolve the data in the request to an internal object mmsdeliverrequest mmsdeliverreq = This.parserequest (Req.getinputstream ());
Recipient shortnumberrecipient = Mmsdeliverreq.getrecipient ();
Recipient originalnumberrecipient = null;
try {//Convert receiver cornet to trombone originalnumberrecipient = Convertshortnumber (shortnumberrecipient); } catch (SQLException e) {//receiver cornet A database exception occurred while converting to trombone, triggering the cache asyncrequestpersistence.getinstance () for the request message. Store (Mmsdeliverre
Q);
Continue other processing of the current request, such as response to client resp.setstatus (202); } private Mmsdeliverrequest Parserequest (InputStream reqinputstream) {mmsdeliverrequest mmsdeliverreq = new MMSDe
Liverrequest ();
Omit other code return mmsdeliverreq; Private recipient Convertshortnumber (recipient shortnumberrecipient) throws Sqlexception {recipient recipent = null;
Omit other code return recipent;
}
}
The Dopost method in Listing 2 detects a database exception that occurs during a cornet conversion, triggering a cache of request messages sent to the MMS by calling the store method of the Asyncrequestpersistence class. Here, the Asyncrequestpersistence class is equivalent to a proxy participant in active object mode. Although this case involves a concurrent environment, the code in Listing 2 shows that the caller code of the Asyncrequestpersistence class does not need to handle multithreaded synchronization issues. This is because multithreaded synchronization issues are encapsulated after the Asyncrequestpersistence class.
The code for the Asyncrequestpersistence class is shown in Listing 3.
Listing 3. MMS Request Cache Entry Class (proxy for Active object mode)
Activeobjectpattern.proxy public class Asyncrequestpersistence implements Requestpersistence {private static final lo
ng one_minute_in_seconds = 60;
Private final Logger Logger;
Private final Atomiclong tasktimeconsumedperinterval = new Atomiclong (0);
Private final Atomicinteger requestsubmittedperiterval = new Atomicinteger (0); Activeobjectpattern.servant private Final Diskbasedrequestpersistence delegate = new Diskbasedrequestpersistenc
E ();
Activeobjectpattern.scheduler private final Threadpoolexecutor Scheduler;
private Static class Instanceholder {final static requestpersistence INSTANCE = new Asyncrequestpersistence ();
Private Asyncrequestpersistence () {logger = Logger.getlogger (Asyncrequestpersistence.class); Scheduler = new Threadpoolexecutor (1, 3, * one_minute_in_seconds, timeunit.seconds,//Activeobjectpattern . Activationqueue new Linkedblockingqueue (), new Threadfactory () {@Override public ThreadNewthread (Runnable r) {Thread T;
t = new Thread (R, "Asyncrequestpersistence");
return t;
}
});
Scheduler.setrejectedexecutionhandler (New Threadpoolexecutor.discardoldestpolicy ());
Start Queue Monitor timed task Timer Monitortimer = new timer (true); Monitortimer.scheduleatfixedrate (New TimerTask () {@Override public void run () {if logger.isinfoenable D ()) {Logger.info ("task count: + Requestsubmittedperiterval +", Queue size: "+ scheduler.ge
Tqueue (). Size () + ", Tasktimeconsumedperinterval:" + tasktimeconsumedperinterval.get () + "MS");
} tasktimeconsumedperinterval.set (0);
Requestsubmittedperiterval.set (0);
}, 0, one_minute_in_seconds * 1000);
public static Requestpersistence getinstance () {return instanceholder.instance;
@Override public void Store (final mmsdeliverrequest request) {* * * encapsulates the call to the store method into a Methodrequest object and is stored in a buffer. *//ACTIVEOBJECTPAttern. Methodrequest callable Methodrequest = new callable () {@Override public Boolean call () throws Exception {Lon
G start = System.currenttimemillis ();
try {delegate.store (request);
finally {Tasktimeconsumedperinterval.addandget (System.currenttimemillis ()-start);
return boolean.true;
}
};
Scheduler.submit (methodrequest);
Requestsubmittedperiterval.incrementandget ();
}
}
The interface implemented by the Asyncrequestpersistence class Requestpersistence defines an asynchronous method for external exposure of active object: the Store method. Because this case does not care about the result of the request cache, the method has no return value. The code looks like listing 4.
Listing 4. Requestpersistence Interface Source Code
Public interface Requestpersistence {
void Store (mmsdeliverrequest request);
}
The instance variable scheduler of the Asyncrequestpersistence class corresponds to the scheduler participant instance in active object mode. Here we directly use the Threadpoolexecutor in the executor framework introduced by JDK1.5. In the instantiation of the Threadpoolexecutor class, the 5th parameter of its constructor (blockingqueue<runnable> workqueue) We specified a bounded blocking queue: New Linkedblockingqueue<runnable> (200). The queue corresponds to an Activationqueue participant instance in active object mode.
The instance variable delegate of the Asyncrequestpersistence class corresponds to the servant participant instance in active object mode.
The store method of the Asyncrequestpersistence class uses an anonymous class to generate a java.util.concurrent.Callable instance methodrequest. This instance is equivalent to the Methodrequest participant instance in active object mode. With closures (Closure), this instance encapsulates contextual information about the store method call, including the invocation parameters, the operation information for the method being invoked, and so on. The store method of the Asyncrequestpersistence class feeds the methodrequest into a buffer (blocking queue) maintained by Threadpoolexecutor by invoking the scheduler's Submit method. Specifically, Threadpoolexecutor is an "approximate" implementation of scheduler participants. Threadpoolexecutor's Submit method is relative to the scheduler Enqueue method, which is used to accept the Methodrequest object for storing it in a buffer. When the number of threads currently used by Threadpoolexecutor is less than the number of its core threads, the tasks received by the Submit method are executed directly by the newly created thread. When the number of threads currently in use by Threadpoolexecutor is greater than the number of its core threads, the task received by the Submit method is stored in its maintained blocking queue. However, the threadpoolexecutor of this task-handling mechanism does not prevent us from using it as a scheduler implementation.
The Methodrequest call method invokes the delegate store method to truly implement the request caching function. The class diskbasedrequestpersistence corresponding to the delegate instance is the real implementation of the request message caching feature. The code looks like listing 5.
Listing 5. The source code of the Diskbasedrequestpersistence class
public class Diskbasedrequestpersistence implements Requestpersistence {//responsible for storage management of cached files private final Sectionbaseddiskst
Orage storage = new Sectionbaseddiskstorage ();
Private final Logger Logger = Logger. GetLogger (Diskbasedrequestpersistence.class); @Override public void Store (Mmsdeliverrequest request) {//Request cache file filename string[] filenameparts = Storage.apply4filenam
E (Request);
File File = new file (Filenameparts[0]);
try {objectoutputstream objout = new ObjectOutputStream (new FileOutputStream (file));
try {objout.writeobject (request);
finally {objout.close ();
The catch (FileNotFoundException e) {storage.decrementsectionfilecount (filenameparts[1]);
Logger.error ("Failed to store Request", E);
catch (IOException e) {storage.decrementsectionfilecount (filenameparts[1]);
Logger.error ("Failed to store Request", E); } class Sectionbaseddiskstorage {private Deque sectionnames = new LinkedliSt ();
* * Key->value: Storage subdirectory name-> Directory Cache file Counter * * Private Map Sectionfilecountmap = new HashMap ();
private int maxfilespersection = 2000;
private int maxsectioncount = 100;
Private String Storagebasedir = System.getproperty ("User.dir") + "/VPN";
Private final Object Sectionlock = new Object ();
Public string[] Apply4filename (mmsdeliverrequest request) {String sectionname;
int ifilecount;
Boolean need2removesection = false;
string[] FileName = new string[2];
Synchronized (Sectionlock) {//Get the current storage subdirectory name sectionname = This.getsectionname ();
Atomicinteger FileCount;
FileCount = Sectionfilecountmap.get (sectionname);
Ifilecount = Filecount.get (); The current storage subdirectory is full if (Ifilecount >= maxfilespersection) {if (Sectionnames.size () >= maxsectioncount) {nee
D2removesection = true;
//Create a new storage subdirectory sectionname = This.makenewsectiondir ();
FileCount = Sectionfilecountmap.get (sectionname); } IFIlecount = Filecount.addandget (1);
} Filename[0] = Storagebasedir + "/" + sectionname + "/" + new DecimalFormat ("0000"). Format (Ifilecount) + "-" + Request.gettimestamp (). GetTime ()/1000 + "-" + request.getexpiry () + ". RQ"
;
FILENAME[1] = sectionname;
if (need2removesection) {//delete the oldest storage subdirectory String Oldestsectionname = Sectionnames.removefirst ();
This.removesection (Oldestsectionname);
return fileName; } public void Decrementsectionfilecount (String sectionname) {Atomicinteger filecount = Sectionfilecountmap.get (Sect
Ionname);
if (null!= filecount) {filecount.decrementandget ();
} Private Boolean removesection (String sectionname) {Boolean result = true;
File dir = new file (Storagebasedir + "/" + sectionname);
For (File file:dir.listFiles ()) {result = result && file.delete ();
result = result && dir.delete ();
return result; } privatE string Getsectionname () {string sectionname;
if (Sectionnames.isempty ()) {sectionname = This.makenewsectiondir ();
else {sectionname = Sectionnames.getlast ();
return sectionname;
private String Makenewsectiondir () {string sectionname;
SimpleDateFormat SDF = new SimpleDateFormat ("Mmddhhmmss");
SectionName = Sdf.format (New Date ());
File dir = new file (Storagebasedir + "/" + sectionname);
if (Dir.mkdir ()) {sectionnames.addlast (sectionname);
Sectionfilecountmap.put (SectionName, New Atomicinteger (0));
else {throw new RuntimeException ("Cannot create section dir" + sectionname);
return sectionname;
}
}
}
The caller code of the Methodrequest call method is run in the worker thread maintained by Threadpoolexecutor, which ensures that the store The caller of the method and the true performer are running in separate threads: The server worker thread is responsible for triggering the request message cache, and the worker thread maintained by Threadpoolexecutor is responsible for serializing the request message into the disk file.
The Apply4filename method of the Sectionbaseddiskstorage class called in the store method of the Diskbasedrequestpersistence class contains some multithreaded synchronization control codes (see Listing 5). This part of the control is not visible to code outside of the class because it is encapsulated in the inner class of the diskbasedrequestpersistence. Therefore, the Asyncrequestpersistence caller code does not know this detail, which embodies the encapsulation of the active object mode for concurrent access control. Summary
This article describes the intent and architecture of the active object schema and demonstrates the code implementation of the pattern in a practical case. The following article evaluates the active object pattern and combines the case to describe some of the things you need to be aware of when you actually use active object mode.
Thank long Zhang for the revision of this article.