Akka actors are great when we are looking for a scalable real-time transaction processing (yes, this is the actual definition using some big words). Actually, it’s really great for some background processing because you can create many instances without actually worrying about concurrency and parallelism.
The code
We have a simple application for processing the uploaded file. We accept the file, parse it (simple txt file), calculate the values and save them in some database. We could have everything in one actor, but it’s much better to split it in multiple actors and create a pipeline. Each actor does exactly one thing. We have much more clean code and at the same time, testing it is much easier.
We have (for this demonstration) 2 actors. One reads the files to a List and sends it to another actor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public class ReadFileActor extends UntypedActor { @Override public void onReceive(Object m) throws Exception { if(m instanceof ReadFileMessage) { ReadFileMessage message = (ReadFileMessage) m; List<Double> numbers = this.readNumbersFromFile(message.filename); // some simple method to read file final ActorSelection actor = context().system().actorSelection("user/calculator-actor"); actor.tell(new NumbersMessage(numbers)); } else { unhandled(m); } } public static class ReadFileMessage { public final String filename; public ReadFileMessage(String filename) { this.filename = filename; } } } |
The second actor gets a the List of numbers and calculates the sum of them.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class CalculatorActor extends UntypedActor { @Override public void onReceive(Object m) throws Exception { if(m instanceof NumbersMessage) { NumbersMessagemessage = (NumbersMessage) m; Double sum = this.sumNumbers(message.numbers); // send to another actor... } else { unhandled(m); } } public static class NumbersMessage { public final List<Double> numbers; public ReadFileMessage(List<Double> numbers) { this.numbers= numbers; } } } |
If we used this code, we would quickly discover problems. When I tested it with VisualVM for memory leaks, I quickly discovered a memory leak with List
Immutable collections
When passing object between actors we need to follow few guidelines. If we brake them, we can face memory leaks and consequentially app crashes. One of the guidelines is to use Immutable collections. If we pass them between actors, they have to be Immutable. What are the advantages of Immutable objects?
- Thread-safe – so they can be used by many threads with no risk of race conditions.
- Doesn’t need to support mutation, and can make time and space savings with that assumption.
- All immutable collection implementations are more memory-efficient than their mutable siblings (analysis)
- Can be used as a constant, with the expectation that it will remain fixed.
There are many implementations of Immutable collections and one of the best ones is in Guava.
Improved code
We have to use ImmutableList to create a list of numbers for passing between actors.
1 2 3 4 |
... ReadFileMessage message = (ReadFileMessage) m; ImmutableList<Double> numbers = this.readNumbersFromFile(message.filename); ... |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
... NumbersMessagemessage = (NumbersMessage) m; // numbers is now ImmutableList<Double> Double sum = this.sumNumbers(message.numbers); .... public static class NumbersMessage { public final ImmutableList<Double> numbers; public ReadFileMessage(ImmutableList<Double> numbers) { this.numbers= numbers; } } |
Rerunning VisualVM confirmed that memory leak was resolved. Great.