Threading refers to the practice of executing programming processes concurrently to improve application performance. While it's not that common to work with threads directly in business applications, they're used all the time in Java frameworks.
As an example, frameworks that process a large volume of information, like Spring Batch, use threads to manage data. Manipulating threads or CPU processes concurrently improves performance, resulting in faster, more efficient programs.
Find your first thread: Java's main() method
Even if you've never worked directly with Java threads, you've worked indirectly with them because Java's main() method contains a main Thread. Anytime you've executed the main()
method, you've also executed the main Thread
.
Studying the Thread
class is very helpful for understanding how threading works in Java programs. We can access the thread that is being executed by invoking the currentThread().getName()
method, as shown here:
public class MainThread {
public static void main(String... mainThread) {
System.out.println(Thread.currentThread().getName());
}
}
This code will print “main,” identifying the thread currently being executed. Knowing how to identify the thread being executed is the first step to absorbing thread concepts.
The Java thread lifecycle
When working with threads, it's critical to be aware of thread state. The Java thread lifecycle consists of six thread states:
- New: A new
Thread()
has been instantiated. - Runnable: The
Thread
'sstart()
method has been invoked. - Running: The
start()
method has been invoked and the thread is running. - Suspended: The thread is temporarily suspended, and can be resumed by another thread.
- Blocked: The thread is waiting for an opportunity to run. This happens when one thread has already invoked the
synchronized()
method and the next thread must wait until it's finished. - Terminated: The thread's execution is complete.
There's more to explore and understand about thread states, but the information in Figure 1 is enough for you to solve this Java challenge.
Concurrent processing: Extending a Thread class
At its simplest, concurrent processing is done by extending a Thread
class, as shown below.
public class InheritingThread extends Thread {
InheritingThread(String threadName) {
super(threadName);
}
public static void main(String... inheriting) {
System.out.println(Thread.currentThread().getName() + " is running");
new InheritingThread("inheritingThread").start();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running");
}
}
Here we're running two threads: the MainThread
and the InheritingThread
. When we invoke the start()
method with the new inheritingThread()
, the logic in the run()
method is executed.
We also pass the name of the second thread in the Thread
class constructor, so the output will be:
main is running.
inheritingThread is running.
The Runnable interface
Rather than using inheritance, you could implement the Runnable interface. Passing Runnable
inside a Thread
constructor results in less coupling and more flexibility. After passing Runnable
, we can invoke the start()
method exactly like we did in the previous example:
public class RunnableThread implements Runnable {
public static void main(String... runnableThread) {
System.out.println(Thread.currentThread().getName());
new Thread(new RunnableThread()).start();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
Non-daemon vs daemon threads
In terms of execution, there are two types of threads:
- Non-daemon threads are executed until the end. The main thread is a good example of a non-daemon thread. Code in
main()
will be always be executed until the end, unless aSystem.exit()
forces the program to complete. - A daemon thread is the opposite, basically a process that is not required to be executed until the end.
Remember the rule: If an enclosing non-daemon thread ends before a daemon thread, the daemon thread won't be executed until the end.
To better understand the relationship of daemon and non-daemon threads, study this example:
import java.util.stream.IntStream;
public class NonDaemonAndDaemonThread {
public static void main(String... nonDaemonAndDaemon) throws InterruptedException {
System.out.println("Starting the execution in the Thread " + Thread.currentThread().getName());
Thread daemonThread = new Thread(() -> IntStream.rangeClosed(1, 100000)
.forEach(System.out::println));
daemonThread.setDaemon(true);
daemonThread.start();
Thread.sleep(10);
System.out.println("End of the execution in the Thread " +
Thread.currentThread().getName());
}
}
In this example I've used a daemon thread to declare a range from 1 to 100,000, iterate all of them, and then print. But remember, a daemon thread won't complete execution if the non-daemon's main thread finishes first.
The output will proceed as follows:
- Start of execution in the main thread.
- Print numbers from 1 to possibly 100,000.
- End of execution in the main thread, very likely before iteration to 100,000 completes.
The final output will depend on your JVM implementation.
And that brings me to my next point: threads are unpredictable.
Thread priority and the JVM
It's possible to prioritize thread execution with the setPriority
method, but how it's handled depends on the JVM implementation. Linux, MacOS, and Windows all have different JVM implementations, and each will handle thread priority according to its own defaults.
The thread priority you set does influence the order of thread invocation, however. The three constants declared in the Thread
class are:
/**
* The minimum priority that a thread can have.
*/
public static final int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public static final int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public static final int MAX_PRIORITY = 10;
Try running some tests on the following code to see what execution priority you end up with:
public class ThreadPriority {
public static void main(String... threadPriority) {
Thread moeThread = new Thread(() -> System.out.println("Moe"));
Thread barneyThread = new Thread(() -> System.out.println("Barney"));
Thread homerThread = new Thread(() -> System.out.println("Homer"));
moeThread.setPriority(Thread.MAX_PRIORITY);
barneyThread.setPriority(Thread.NORM_PRIORITY);
homerThread.setPriority(Thread.MIN_PRIORITY);
homerThread.start();
barneyThread.start();
moeThread.start();
}
}
Even if we set moeThread
as MAX_PRIORITY
, we cannot count on this thread being executed first. Instead, the order of execution will be random.
Take the Java threads challenge!
You've learned just a little bit about threads, but it's enough for this post's Java challenge.
To start, study the following code:
public class ThreadChallenge {
private static int wolverineAdrenaline = 10;
public static void main(String... doYourBest) {
new Motorcycle("Harley Davidson").start();
Motorcycle fastBike = new Motorcycle("Dodge Tomahawk");
fastBike.setPriority(Thread.MAX_PRIORITY);
fastBike.setDaemon(false);
fastBike.start();
Motorcycle yamaha = new Motorcycle("Yamaha YZF");
yamaha.setPriority(Thread.MIN_PRIORITY);
yamaha.start();
}
static class Motorcycle extends Thread {
Motorcycle(String bikeName) { super(bikeName); }
@Override public void run() {
wolverineAdrenaline++;
if (wolverineAdrenaline == 13) {
System.out.println(this.getName());
}
}
}
}
What will be the output of this code? Analyze the code and try to determine the answer for yourself, based on what you've learned.
A. Harley Davidson
B. Dodge Tomahawk
C. Yamaha YZF
D. Indeterminate
What just happened?
In the above code, we created three threads. The first thread is Harley Davidson
, and we assigned this thread the default priority. The second thread is Dodge Tomahawk
, assigned MAX_PRIORITY
. The third is Yamaha YZF
, with MIN_PRIORITY
. Then we started the threads.
In order to determine the order the threads will run in, you might first note that the Motorcycle
class extends the Thread
class, and that we've passed the thread name in the constructor. We've also overridden the run()
method with a condition: if wolverineAdrenaline is equals to 13
.
Even though Yamaha YZF
is the third thread in our order of execution, and has MIN_PRIORITY
, there's no guarantee that it will be executed last for all JVM implementations.
You might also note that in this example we set the Dodge Tomahawk
thread as daemon
. Because it's a daemon thread, Dodge Tomahawk
may never complete execution. But the other two threads are non-daemon by default, so the Harley Davidson
and Yamaha YZF
threads will definitely complete their execution.
To conclude, the result will be D: Indeterminate, because there is no guarantee that the thread scheduler will follow our order of execution or thread priority.
Remember, we can't rely on program logic (order of threads or thread priority) to predict the JVM's order of execution.
Video challenge! Debugging variable arguments
Debugging is one of the easiest ways to fully absorb programming concepts while also improving your code. In this video you can follow along while I debug and explain the thread behavior challenge:
Common mistakes with Java threads
- Invoking the
run()
method to try to start a new thread. - Trying to start a thread twice (this will cause an
IllegalThreadStateException
). - Allowing multiple processes to change the state of an object when it shouldn't change.
- Writing program logic that relies on thread priority (you can't predict it).
- Relying on the order of thread execution--even if we start a thread first, there is no guarantee it will be executed first.
What to remember about Java threads
- Invoke the
start()
method to start aThread
. - It's possible to extend the
Thread
class directly in order to use threads. - It's possible to implement a thread action inside a
Runnable
interface. - Thread priority depends on the JVM implementation.
- Thread behavior will always depend on the JVM implementation.
- A daemon thread won't complete if an enclosing non-daemon thread ends first.
Learn more about Java and Java threads
- Modern threading: A Java concurrency primer introduces
java.util.concurrent
and answers common questions for developers new to Java concurrency. - Modern threading for not-quite-beginners offers more advanced tips and best practices for working with
java.util.concurrent
. - Get quick code tips: Read all of Rafael's articles in the InfoWorld Java Challengers series.
- Check out all the videos in Rafael's Java Challengers video playlist.
- Find even more Java Challengers on Rafael's Java Challengers blog and in his book, with more than 70 code challenges.
This story, "Thread behavior in the JVM" was originally published by JavaWorld.