For software developers, debugging an indeterminate flaw in a multithreaded application is the most painful task. So, like most people, I love using functional languages such as Erlang and Scala to do concurrent programming.
Both Scala and Erlang adopt a role model for concurrent programming, with no threading concept. The innovation around the role model is not limited to the language itself, but the role model can be used by Kilim and other java-based role frameworks.
Kilim's use of the role model is straightforward, and you'll see later that the library makes building concurrent applications incredibly simple.
Multi-Nuclear challenge
In 2005, Herb Sutter wrote a still quite famous article "The Free Lunch is over:a fundamental Turn toward concurrency in Software". In this article, he rejects the idea that Moore's law will continue to promote the increasing CPU clock rate.
Sutter predicted the end of the "free Lunch", and it was no longer possible to lift the performance of the software application with the increasingly fast chip. Instead, he argues that a significant increase in application performance will require the use of multi-core chip architectures.
It turns out that he is right. Chip makers have reached a rigid limit, with chip rates already stable at 3.5 GHz for years. Moore's law continues to be met in the multi-core field as manufacturers increase the number of cores on their chips more and more quickly.
Sutter also mentions that concurrent programming will enable developers to leverage multi-core architectures. But, he adds, "We desperately need a more advanced concurrency programming model than the programming models available in various languages today." ”
The basic programming model for languages such as Java is thread based. Although multithreaded applications are not difficult to write, they still face many challenges to write correctly. One of the difficulties with concurrent programming is the use of threads to consider concurrency. There are many concurrent models, and a particularly interesting model that has been recognized by the Java community is the role model.
Role Model
The role model is a different mode of concurrent process modeling. Unlike threads that interact with locks through shared memory, the role model leverages the "role" concept and uses mailboxes to deliver asynchronous messages. Here, mailboxes are similar to real-life mailboxes, and messages can be stored and retrieved by other roles for processing. Mailboxes effectively separate processes from each other, rather than sharing variables in memory.
Roles act as separate and completely different entities and do not share memory for communication. In fact, a role can only communicate through a mailbox. There are no locks and synchronized blocks in the role model, so there are no problems caused by them, such as deadlocks, serious loss updates. Also, roles can work concurrently, rather than in a sequential fashion. Therefore, roles are more secure (no locks and synchronizations are required), and the role model itself can handle coordination issues. In essence, the role model makes concurrent programming simpler.
The role model is not a new concept, it has existed for a long time. The concurrency model for some languages, such as Erlang and Scala, is role-based rather than thread based. In fact, Erlang's success in the enterprise environment (Erlang was created by Ericsson and has a long history in the telecommunications field) has undoubtedly made the role model more popular, more exposed, and a viable option for other languages. Erlang is an excellent example of a more secure concurrent programming approach to role models.
Unfortunately, the role model is not implanted into the Java platform, but we can use it in a variety of ways. The openness of the JVM to alternative languages means that you can leverage roles (see Resources for Groovy's role library Gpars) through the Java platform language, such as Scala or Groovy. Alternatively, you can try a Java-based library that supports role models, such as Kilim.
roles in the Kilim
Kilim is a library written using Java, incorporating the concept of role models. In Kilim, the role is represented by the Task type of Kilim. Tasks are lightweight threads that communicate with other tasks through the Kilim mailbox type.
Mailbox can accept any type of "message". For example, the Mailbox type accepts java.lang.Object. A Task can send a string message or even a custom message type, which is entirely up to you.
In Kilim, all entities are bundled together by method signatures, and if you need to perform several actions at the same time, you can specify the behavior in one method and extend the signature of the method to throw the pausable. Therefore, creating concurrent classes in Kilim is as simple as implementing runnable or extending thread in Java. Only additional entities that use Runnable or Thread (such as keyword synchronized) are less.
Finally, the magic of Kilim is implemented by a later process called Weaver, which converts the byte code of the class. A method containing pausable throws words is processed by a dispatcher at run time, which is included in the Kilim library. The scheduler processes a limited number of kernel threads. This tool can be used to handle more lightweight threads, which maximizes the speed of context switching and startup. The stack for each thread is automatically managed.
In essence, Kilim makes it easy and easy to create concurrent processes: simply extend from the Kilim Task type and implement the Execute method. After compiling a newly created class that supports concurrency, you can achieve significant performance improvements by running Kilim Weaver on it.
Kilim was originally a foreign language, but it brought a huge payoff. Role models (and later Kilim) make it simpler and more secure to write asynchronous operations objects that depend on similar objects. You can do the same thing with Java's basic threading model, such as extending thread, but this is more challenging because it brings you back to the world of locks and synchronizations. In short, translating your concurrent programming model into roles makes multithreaded applications easier to encode. Kilim Combat
In the Kilim role model, messages are routed through Mailbox between processes. In many cases, you can view Mailbox as a queue. Processes can add items to mailboxes or get items from mailboxes, and they can be either blocked or non-blocking (blocking objects are lightweight processes implemented by the underlying Kilim, rather than kernel threads).
As an example of using mailboxes in Kilim, I wrote two roles (Calculator and deferreddivision) that were extended from the Kilim task type. These classes will work together in a concurrent fashion. The Deferreddivision object creates a divisor and a divisor, but it does not attempt to divide the two numbers. We know that the division operation is very resource intensive, so the Deferreddivision object will require the Calculator type to handle the task.
The two roles communicate through a shared Mailbox instance that accepts a calculation type. This type of message is very simple--the divisor and divisor have been provided, and Calculator will then perform the calculation and set the corresponding answer. Calculator then put the calculation instance back into the shared Mailbox.
Calculation
Listing 1 shows this simple calculation type. You will notice that this type does not require any special Kilim code. In fact, it's just an ordinary Java bean.
Listing 1. A calculation type of message
Import Java.math.BigDecimal;
public class Calculation {
private BigDecimal dividend;
Private BigDecimal divisor;
Private BigDecimal answer;
Public calculation (BigDecimal dividend, BigDecimal divisor) {
super ();
This.dividend = dividend;
This.divisor = divisor;
}
Public BigDecimal Getdividend () {return
dividend;
}
Public BigDecimal Getdivisor () {return
divisor;
}
public void Setanswer (BigDecimal ans) {
this.answer = ans;
}
Public BigDecimal Getanswer () {return
answer;
}
Public String Printanswer () {return
' answer of ' + dividend + ' divided by ' + divisor +
' is ' + answer;
}
}
|
deferreddivision
The
Deferreddivision class uses a Kilim-specific class. The class does a number of things, but in general it works very simply: Use random numbers (type BigDecimal) to create instances of calculation and send them to the Calculator role. Also, the class checks the shared mailbox to see if any calculation are in it. If there is an answer to a calculation instance that is retrieved, deferreddivision prints it.
Listing 2. Deferreddivision create a random divisor and dividend
Import Java.math.BigDecimal;
Import Java.math.MathContext;
Import Java.util.Date;
Import Java.util.Random; Import Kilim.
Mailbox; Import Kilim.
pausable; Import Kilim.
Task;
public class Deferreddivision extends Task {private mailbox<calculation> Mailbox;
Public deferreddivision (mailbox<calculation> Mailbox) {super ();
This.mailbox = mailbox; @Override public void Execute () throws Pausable, Exception {Random numbergenerator = new Random (new Date (). GetTime
());
Mathcontext context = new Mathcontext (8);
while (true) {System.out.println ("I need to know the answer of something"); MAILBOX.PUTNB (new calculation (), New BigDecimal (Numbergenerator.nextdouble (), context), New BigDecimal (Numbergene
Rator.nextdouble (), context));
Task.sleep (1000); Calculation answer = MAILBOX.GETNB (); No block if (answer!= null && answer.getanswer ()!= null) {System.out.println ("answer is:" + ANSWER.P
Rintanswer ());
}
}
}
} |
As you can see from Listing 2, the Deferreddivision class extends the Task type of Kilim, which actually mimics the role model. Note that the class also overwrites the task's Execute method, which throws pausable by default. Therefore, the operation of execute will be performed under the Kilim scheduling program. That is, Kilim will ensure that execute runs in a safe way in parallel.
Inside the Execute method, Deferreddivision creates instances of calculation and places them in mailbox. It accomplishes this task in a non-blocking manner using the PUTNB method.
After populating the mailbox, deferreddivision into hibernation-note that unlike a dormant kernel thread, it is a lightweight thread managed by Kilim. When the character wakes up, as mentioned earlier, it looks for any calculation in the mailbox. This call is also non-blocking, which means that GETNB can return null. If Deferreddivision finds a calculation instance, and the Getanswer method of the instance has a value (that is, not a calculation instance that has been processed by the Calculator type), it prints the value to the console.
Calculator
The other end of the Mailbox is Calculator. Similar to the deferreddivision role defined in Listing 2, Calculator extends the Kilim task and implements the Execute method. Be sure to note that all two roles share the same Mailbox instance. They cannot communicate with different mailbox, they need to share an instance. Accordingly, all two roles accept a type Mailbox through their constructors.
Listing 3. Final actual operational role: Calculator
Import Java.math.RoundingMode;
Import Kilim. Mailbox;
Import Kilim. pausable;
Import Kilim. Task;
public class Calculator extends task{
private mailbox<calculation> Mailbox;
Public Calculator (mailbox<calculation> Mailbox) {
super ();
This.mailbox = mailbox;
}
@Override public
Void Execute () throws Pausable, Exception {while
(true) {
Calculation calc = mailbox.get (); Blocks
if (calc.getanswer () = null) {
calc.setanswer (Calc.getdividend (). Divide (Calc.getdivisor (), 8,
roundingmode.half_up));
SYSTEM.OUT.PRINTLN ("Calculator determined Answer");
MAILBOX.PUTNB (calc);
Task.sleep (1000);}}
|
The Calculator Execute method, like Deferreddivision's corresponding method, constantly loops through the items in the shared mailbox. The difference is that Calculator calls the Get method, which is a blocking call. Accordingly, when a calculation "message" is displayed, it performs the requested division operation. Finally, Calculator the modified calculation back into the Mailbox (in a non-blocking manner), and then goes into hibernation. Hibernate calls in two roles are used only to simplify the reading of the console. Kilim's Weaver
In the front, I mentioned that Kilim performs bytecode operations through its weaver. This is a simple post processing process that you run after you compile the class. Weaver then adds some special code to the various classes and methods that contain the pausable tag.
Calling Weaver is very simple. For example, in Listing 4, I use Ant to invoke Weaver. All I need to do is tell Weaver where I need the class and where to place the generated bytecode. In this example, I let Weaver change the class in the Target/classes dictionary and write the resulting bytecode back to the dictionary.
Listing 4. Ant calls Kilim's Weaver
<target name= "weave" depends= "compile" description= "handles Kilim byte code weaving" >
<java classname= " Kilim.tools.Weaver "fork=" yes "> <classpath refid=" classpath "/> <arg value=
"-D "/>
< Arg value= "./target/classes"/>
<arg line= "/target/classes"/>
</java>
</target>
|
After changing the code, I can take advantage of Kilim at run time, as long as I include its. jar file in the classpath.
Using Kilim at run time
Applying these two roles to the real world is like applying two normal Thread in Java code. You use the same shared Sharedmailbox instance to create and extend two role instances, and then call the Start method to actually set them.
Listing 5. A simple procedure
Import Kilim. Mailbox;
Import Kilim. Task;
public class Calculationcooperation {public
static void Main (string[] args) {
mailbox<calculation> Sharedmailbox = new mailbox<calculation> ();
Task deferred = new deferreddivision (sharedmailbox);
Task Calculator = new Calculator (sharedmailbox);
Deffered.start ();
Calculator.start ();
}
|
Running both roles will get the output shown in Listing 6. If you run this code, your output may be different, but the logical order of the activities will remain unchanged. In Listing 6, deferreddivision requests the calculation, Calculator uses an answer as a response.
Listing 6. Your output will be different-different roles are not immutable
[Java] I need to know the answer of something
[Java] Calculator determined answer
[Java] answer is:the answer of 0.36477 377 divided by 0.96829189 are 0.37671881
[Java] I need to know the answer of something
[Java] Calculator determined Answer
[Java] answer is:the answer of 0.40326269 divided by 0.38055487 was 1.05967029
[Java] I need to know the A Nswer of Something
[Java] Calculator determined answer
[Java] answer is:the answer 0.16258913 by 0.918 54403 is 0.17700744
[Java] I need to know the answer of something
[Java] Calculator determined answer
[Java ] Answer is:the Answer of 0.77380722 divided by 0.49075363 is 1.57677330
|