8 min read

In this article by Javier Fernández González, the author of the book, Mastering Concurrency Programming with Java 9 – Second Edition, we will see the execution threads are the core of concurrent applications. When you implement a concurrent application, no matter the language, you have to create different execution threads that run in parallel in a non-deterministic order unless you use a synchronization element (such as a semaphore). In Java you can create execution threads in two ways:

  • Extending the Thread class
  • Implementing the Runnable interface

In this article, you will learn how to use these elements to implement concurrent applications in Java.

(For more resources related to this topic, see here.)

Introduction

Nowadays, computer users (and mobile and tablet users too) use different applications at the same time when they work with their computers. They can be writing a document with the word processor while they’re reading the news or posting on the social network and listening to music. They can do all these things at the same time because modern operating systems supports multiprocessing. They can execute different tasks at the same time. But inside an application, you can also do different things at the same time. For example, if you’re working with your word processor, you can save the file while you’re putting a text with bold style. You can do this because the modern programming languages that are used to write those applications allow programmers to create multiple execution threads inside an application. Each execution thread executes a different task, so you can do different things at the same time.

Java implements execution threads using the Thread class. You can create an execution thread in your application using the following mechanisms:

  • You can extend the Thread class and override the run() method
  • You can implement the Runnable interface and pass an object of that class to the constructor of a Thread object

In both the cases, you will have a Thread object, but the second approach is recommended over the first one. Its main advantages are:

  • Runnable is an interface: You can implement other interfaces and extend other class. With the Thread class you can only extend that class.
  • Runnable objects can not only be executed with threads but also in other Java concurrency objects as executors. This gives you more flexibility to change your concurrent applications.
  • You can use the same Runnable object with different threads.

Once you have a Thread object, you must use the start() method to create a new execution thread and execute the run() method of Thread. If you call the run() method directly, you will be calling a normal Java method and no new execution thread will be created. Let’s see the most important characteristics of threads in the Java programming language.

Threads in Java: characteristics and states

The first thing we have to say about threads in Java is that all Java programs, concurrent or not, have one Thread called the main thread. As you may know, a Java SE program starts its execution with the main() method. When you execute that program, the Java Virtual Machine (JVM) creates a new Thread and executes the main() method in that thread. This is the unique thread in the non-concurrent applications and the first one in the concurrent ones.

In Java, as with other programming languages, threads share all the resources of the application, including memory and open files. This is a powerful tool because they can share information in a fast and easy way, but it must be done using adequate synchronization elements to avoid data race conditions.

All the threads in Java have a priority. It’s an integer value that can be between the Thread.MIN_PRIORITY and Thread.MAX_PRIORITY values (actually, their values are 1 and 10). By default, all the threads are created with the priority, Thread.NORM_PRIORITY (actually, its value is 5). You can use the setPriority() method to change the priority of a Thread (it can throw a SecurityException exception if you are not allowed to do that operation) and the getPriority() method to get the priority of a Thread. This priority is a hint to the Java Virtual Machine and to the underlying operating system about the preference between the threads, but it’s not a contract. There’s no guarantee about the order of execution of the threads. Normally, threads with a higher priority will be executed before the threads with lower priority but, as I told you before, there’s no guarantee about this.

You can create two kind of threads in Java:

  • Daemon threads
  • Non-daemon threads

The difference between them is how they affect the end of a program. A Java program ends its execution when one of the following circumstances occurs:

  • The program executes the exit() method of the Runtime class and the user has authorization to execute that method
  • All the non-daemon threads of the application have ended its execution, no matter if there are daemon threads running.

With these characteristics, daemon threads are usually used to execute auxiliary tasks in the applications as garbage collectors or cache managers. You can use the isDaemon() method to check whether a thread is a daemon thread or not, and you can use the setDaemon() method to establish a thread as a daemon one. Take into account that you must call this method before the thread starts its execution with the start() method.

Finally, threads can pass through different states depending on the situation. All the possible states are defined in the Thread.States class and you can use the getState() method to get the status of a Thread. Obviously, you can change the status of the thread directly. These are the possible statuses of a thread:

  • NEW: Thread has been created but it hasn’t started its execution yet
  • RUNNABLE: Thread is running in the Java Virtual Machine
  • BLOCKED: Thread is waiting for a lock
  • WAITING: Thread is waiting for the action of the other thread
  • TIME_WAITING: Thread is waiting for the action of the other thread, but this waiting has a time limit
  • THREAD: Thread has finished its execution

Now that we know the most important characteristics of threads in the Java programming language, let’s see the most important methods of the Runnable interface and the Thread class.

The Thread class and the Runnable interface

As we mentioned before, you can create a new execution thread using one of the following two mechanisms:

  • Extend the Thread class and override its run() method.
  • Implement the Runnable interface and pass an instance of that object to the constructor of a Thread object.

Java good practices recommend the utilization of the second approach over the first one, and that will be the approach we will use in this article and in the whole book.

The Runnable interface only defines one method: the run() method. This is the main method of every thread. When you start a new execution of the start() method, it will call the run() method (of the Thread class or of the Runnable object passed as a parameter in the constructor of the Thread class).

The Thread class, on the contrary, has a lot of different methods. It has a run() method that you must override if you implement your thread extending the Thread class and the start() method that you must call to create a new execution thread. These are the other interesting methods of the Thread class:

  • Methods to get and set information of a Thread:
    • getId(): This method returns the identifier of the Thread. The thread identifier is a positive integer number, assigned when a thread is created. It is unique during its lifetime and it can’t be changed.
    • getName()/setName(): This method allows you to get or set the name of the Thread. This name is a String that can also be established in the constructor of the Thread class.
    • getPriority()/setPriority(): You can use these methods to obtain and establish the priority of the Thread class. We explained before in this article how Java manages the priority of its threads.
    • isDaemon()/setDaemon(): This method allows you to obtain and establish the condition of a daemon of the Thread. We have explained how this condition works before.
    • getState(): This method returns the state of the Thread. We explained before all the possible states of a Thread.
  • interrupt()/interrupted()/isInterrupted(): The first method is used to indicate to Thread that you’re are requesting the end of its execution. The other two methods can be used to check the interrupt status. The main difference between those methods is that one clears the value of the interrupted flag when it’s called and the other one does not. A call to the interrupt() method doesn’t end the execution of a Thread. It is the responsibility of the Thread to check the status of that flag and respond accordingly.
  • sleep(): This method allows you to suspend the execution of the Thread for a period of time. It receives a long value, that is, the number of milliseconds that you want to suspend the execution of the Thread for.
  • join(): This method suspends the execution of the thread that makes the call until the end of the execution of the Thread that is used to call the method. You can use this method to wait for the finalization of other Thread.
  • setUncaughtExceptionHandler(): This method is used to establish the controller of the unchecked exceptions that can occur while you’re executing the threads.
  • currentThread(): This is a static method of the Thread class that returns the Thread object that is actually executing this code.

Summary

In this article, you learned the threads in Java and how the Thread class and the Runnable interface work.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here