Today we are talking about threads in Java. Threads are an essential means to implement concurrent algorithms and are therefore a key part of efficient everyday data processing. Imagine having a webserver without threads serving only one customer at a time: Every customer would need to pull a number for every single web request they make and our online shop would have a bad time.
So let’s head into it!
In modern days – Java threads with Lambda expressions
Since Java 8 we have been blessed with Lambda expression. Basically Lambda expressions serve the purpose of unnamed functions and are written as shown here:
(param1, param2, [...], paramN) -> { statement1; statement2; [...] statementN; }
If you just need one statement, it looks even better:
(param1, [...], paramN) -> statement
By harnessing their power we are able to write beautiful first level function style concurrent algorithms.
Let’s assume we want to sort a list with an even number of integers. We know that all ints in the second half of the list are greater than the greates number in the first half. Then we can do this:
import java.util.*;
public class ThreadExampleMain {
public static void main(String[] args) throws InterruptedException {
/* Elements of the 2nd half are larger than in the 1st half, otherwise unsorted. */
List<Integer> test = Arrays.asList(3, 1, 5, 6, 21, 12, 18, 16);
List<Integer> sortedFirstHalf = test.subList(0, 4);
List<Integer> sortedSecondHalf = test.subList(4, 8);
Thread t1 = new Thread(() -> {
Collections.sort(sortedFirstHalf); // or any other more elaborate sorting algorithm
System.out.println("First half sorted: " + sortedFirstHalf);
});
Thread t2 = new Thread(() -> {
Collections.sort(sortedSecondHalf);
System.out.println("Second half sorted: " + sortedSecondHalf);
});
t1.start();
t2.start();
t1.join();
t2.join();
// Merge the sorted list parts together from left to right...
List<Integer> result = new ArrayList<>();
result.addAll(sortedFirstHalf);
result.addAll(sortedSecondHalf);
// ...and show the result.
System.out.println("Result: " + result);
}
}
Notice that we feed the threads in our Java code with Lambdas to tell them what to do. Neat, isn’t it?
Legacy Java Threads with Runnable instances
And how has it been done in the past? When I started programming with Java around 5 and 6 we had to declare a full-blown Runnable
-instance that we subsequently fed to the thread. Runnable
is an interface type which means that during declaration we also had to implement it’s signature method run()
. This method finally contains the functionality that the thread is supposed to run. Let me show what I mean:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class ThreadExampleLegacyMain {
public static void main(String[] args) throws InterruptedException {
/* Elements of the 2nd half are larger than in the 1st half, otherwise unsorted. */
List<Integer> test = Arrays.asList(3, 1, 5, 6, 21, 12, 18, 16);
List<Integer> sortedFirstHalf = test.subList(0, 4);
List<Integer> sortedSecondHalf = test.subList(4, 8);
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
Collections.sort(sortedFirstHalf); // or any other more elaborate sorting algorithm
System.out.println("First half sorted: " + sortedFirstHalf);
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
Collections.sort(sortedSecondHalf);
System.out.println("Second half sorted: " + sortedSecondHalf);
}
});
t1.start();
t2.start();
t1.join();
t2.join();
// Merge the sorted list parts together from left to right...
List<Integer> result = new ArrayList<>();
result.addAll(sortedFirstHalf);
result.addAll(sortedSecondHalf);
// ...and show the result.
System.out.println("Result: " + result);
}
}
It was a bloated mess, but since it’s likely in use in modern day code, we got to list it here.
Released in Java 21: Virtual Threads
Virtual threads are part of Project Loom that aims at bringing lightweight and high-throughput concurrency models to the JVM. These virtual threads don’t run on kernel but on JVM level and thus are fully managed by the JVM. That means you as the coder do not need to keep track of them at all. The JVM will do that for you while you can rely on the coding APIs you already know and love. That is because virtual threads are created almost the same way as the native threads we have seen earlier. Let me show what I mean:
Thread t1 = Thread.ofVirtual.start(() -> {
Collections.sort(sortedFirstHalf);
// [...]
});
// alternatively
Thread t2 = Thread.startVirtualThread(() -> {
Collections.sort(sortedFirstHalf);
// [...]
});
t1.join();
t2.join();
As shown above you the virtual threads API hands you a Thread
instance that you can use in ways you are already familiar with. Here we wait for our virtual threads to terminate.
Another important advantage over conventional threads is their ability to scale. Since the JVM manages both ressources and lifetime of virtual threads it is done in a thoroughly optimized way. In addition, because these threads run directly in the JVM instead of the OS kernel, we can omit a lot of OS ressource management overhead. Both factors result in a significant increase of throughput.
Conclusion
So long. I hope I could help you effectively processing data by leveraging the power of Java threads. Please note that the examples shown above are optimized for the topic’s illustration only. Your way to sort a list of integers is certainly much better. If you’d like to read more about me doing Java, I have a post for you, where I talk about web test automation with Java. If you are already familiar with that and want to try something new, I recommend trying fuzz testing in Java. You will gain a fresh view on test automation and honestly it’s a blast to bomb an application with just random stuff.
Last but not least, if you want to dive in really deep into the world of Java threads, check out this article about Thread Pooling. It is quite advanced, but intuitive and powerful once you get the hang of it.
You have more questions regarding Java, me or anything else? As usual, feel free to drop me a line down below. I read every single one of your comments. Have a great day!