Everybody wants scalable systems. However, writing non-blocking applications in Java is not an easy task. In this session, we'll go over 3 different frameworks for managing multi-treading and concurrency support (Akka, Vertx and Quasar).
3. Multi-threading in Java
• The standard-libraries provide us with excellent low-level
mechanisms to deal with multi-threading scenarios:
• volatile.
• synchronized.
• java.util.concurrent.
• Futures/ CompletableFutures/ Atomic Classes/ etc…
4. Performance
• Usually, when doing low-level synchronization, we encounter
multiple performance issues!
• All stems from shared mutable state!
5. Node.js
• On the other hand, a popular non-Java framework, Node.js,
started gaining a lot of attention due to its “simplicity” and
“performance”.
• And you only have a single main thread for your application.
• (yes, there are multiple background threads in Node.js).
6. Shared Mutable State
• The core reason is that we don’t have shared-mutable state that
requires locking and synchronization!
• The problem can be solved by one of the following:
• All data is immutable (extremely hard in Java).
• Unshared mutable data.
7. Unshared Mutable State
• We are going to discuss unshared mutable state.
• Usually it is provided by frameworks.
• The concept is very old in Java and the most infamous
framework that provided it was EJB.
• We’ll cover, in this session, the modern frameworks:
• Akka.
• Vert.x.
• Quasar.
8. Akka
• Akka is an open source framework for building highly
concurrent, distributed and fault-tolerant applications.
• Written in Scala and provides a decent Java API.
• Based on the famous actor model from Erlang.
9. Actors
• An actor is a message processor which processes messages
asynchronously (usually in a different thread).
• Let’s take a look at an actor…
10. Actor
• To create an actor, you should extend from the UntypedActor
class.
public class SimpleActor extends UntypedActor {
@Override
public void onReceive(Object message) {
System.out.println("Got: " + message);
}
}
class SimpleActor extends Actor {
def receive = {
case msg => println(s"Got: $msg")
}
}
11. Creating Actors
• You must never instantiate actors yourself.
• You should never hold a direct reference to the actor.
• Only Akka does.
• It ensures that mutable members inside the actor are properly
encapsulated and unshared.
• In order to instantiate an actor, we need to work with the
ActorSystem object.
12. Actor
• ActorSystem represents a logical application.
• A configuration environment.
• Creating an ActorSystem (usually one in your app):
ActorSystem as = ActorSystem.create("WelcomeToAkka");
val as = ActorSystem("WelcomeToAkka")
13. actorOf
• The actorOf method creates a new actor:
ActorRef ref = as.actorOf(Props.create(SimpleActor.class),
"simpleActor");
val ref = as.actorOf(Props[SimpleActor], "simpleActor")
• Note the return type.
• It’s ActorRef and not our actor class.
14. ActorRef
• Represents a reference to the actor.
• It is immutable and location transparent.
• Can be sent around in messages.
• Can be obtained for remote/clustered actors.
• Its most useful API: sending a message.
15. Fire and Forget
• Sending a message is done in an asynchronous manner.
• The tell() method sends a message to the actor:
ref.tell("Hello from Main", ActorRef.noSender());
// no need to specify sender, it is Actor.noSender (default param)
ref ! "Hello"
• The message must be immutable!
16. Handling Messages
• If the message is unknown, you should call: unhandled().
• It will send an UnhandledMessage instance to the eventStream.
public void onReceive(Object message) throws Exception {
if (message instanceof String) {
System.out.println("Got: " + message);
} else {
unhandled(message);
}
}
17. Routers
• Routers are actors that dispatch incoming messages to their
routees.
• Implementations available: RoundRobin, RandomRobin,
Broadcast, ConsistentHashing and more.
19. Vert.x
• “Eclipse Vert.x is a tool-kit for building reactive applications on
the JVM.”
• At the core of Vert.x is the reactor pattern (a.k.a. event-loop).
• By default, Vert.x spawns N*2 (where N being the number of
cores) event-loop threads.
• The event-loop thread is responsible for calling your handlers
when there are events.
• Don’t ever block it!
20. Vertx Instances
• At the low level API there is the Vertx instance.
• Usually one in your application.
• Contains several event-loop threads.
• Can be clustered.
Vertx vertx = Vertx.vertx();
21. Verticles
• Usually you’ll work with Verticles (which resemble actors).
• Come in 3 flavors:
• Standard verticles.
• Worker verticles.
• Multi-threaded worker verticles.
• We’ll focus here on standard verticles.
22. Standard Verticle
• A standard verticle is assigned to a specific event-loop thread.
• It means that your verticle code can be written without
synchronization or lock management.
• However, how to perform I/O?
• Use Vert.x provided APIs.
23. The Event Bus
• Each Vertx instance has an event bus.
• Through it, different verticles can communicate.
• Supports both publish-subscribe and point-to-point.
24. Async Ops
• Vert.x provides many wrappers and utilities for I/O operations:
• HTTP (server and client).
• Scheduling.
• JDBC.
• NoSQL DBs.
• Mail.
• RabbitMQ.
• STOMP.
• Kafka.
25. Example
• Here is an example of sending an HTTP request (HTTP client).
HttpClient client = vertx.createHttpClient();
client.getNow(8080, "trainologic.com", "/technologies/java/", response ->
{
System.out.println("Trainologic Java status code: " +
response.statusCode());
}
);
27. Quasar
• Quasar aims to provide a simple and performant multi-threading
solution by providing “green-threads”.
• Green threads (called fibers in Quasar jargon) are user-space
threads which have no overhead.
• They actually shared the Thread API thus enabling abstraction
over kernel/user threads.
28. Fibers
• Fibers allow you to avoid the callback hell.
• So, if you have an interface with blocking API:
• String foo(int i);
• Fiber implementation:
@Suspendable
public int foo(final String arg) throws IOException, InterruptedException {
try { return new FiberAsync<Integer, IOException>() {
@Override
protected void requestAsync() {
asyncAPI.asyncInvocation(arg, new SomeHandler<Integer>() {
public void success(Integer result) {
asyncCompleted(result);
}
…
29. How Does it Work?
• Fibers are implemented by continuations!
• The ability to suspend and snapshot the stack of the execution.
• Allowing for cheap context-switching.
• Quasar can do it with either build-time instrumentation or
dedicated Java-agent.
• Don’t forget to mark “blocking” methods as @Suspendable!
30. Features
• Quasar provides:
• Languages: Java, Kotlin & Clojure.
• Good Actor System.
• Clustering (Galaxy) – basic support.
• Integration with Web (Comsat). E.g.:
• HTTP
• Spring
• Kafka
• Servlet Containers…
31. Discussion
• Maturity – Akka and Vert.x quite mature.
• Features – Akka provides the most complete solution. Can be a
bit harder in Java than Vert.x.
• Simplicity – Quasar offers the most “readable” and simple
solution.
• Performance – May vary. All can be very performant!
• Design – all affect your application on the whole!!
32. Conclusion
• Concurrency is hard!
• Shared mutable state is hard!
• Non-blocking is hard!
• Perhaps going immutable and pure-functional is the way?