Creating, running, and setting the characteristics of a thread
In this recipe, we will learn how to do basic operations over a thread using the Java API. As with every element in the Java language, threads are objects. We have two ways of creating a thread in Java:
- Extending the
Thread
class and overriding the run()
method. - Building a class that implements the
Runnable
interface and the run()
method and then creating an object of the Thread
class by passing the Runnable
object as a parameter--this is the preferred approach and it gives you more flexibility.
In this recipe, we will use the second approach to create threads. Then, we will learn how to change some attributes of the threads. The Thread
class saves some information attributes that can help us identify a thread, know its status, or control its priority. These attributes are:
- ID: This attribute stores a unique identifier for each thread.
- Name: This attribute stores the name of the thread.
- Priority: This attribute stores the priority of the
Thread
objects. In Java 9, threads can have priority between 1 and 10, where 1 is the lowest priority and 10 is the highest. It's not recommended that you change the priority of the threads. It's only a hint to the underlying operating system and it doesn't guarantee anything, but it's a possibility that you can use if you want. - Status: This attribute stores the status of a thread. In Java, a thread can be present in one of the six states defined in the
Thread.State
enumeration: NEW
, RUNNABLE
, BLOCKED
, WAITING
, TIMED_WAITING
, or TERMINATED
. The following is a list specifying what each of these states means:NEW
: The thread has been created and it has not yet startedRUNNABLE
: The thread is being executed in the JVMBLOCKED
: The thread is blocked and it is waiting for a monitorWAITING
: The thread is waiting for another threadTIMED_WAITING
: The thread is waiting for another thread with a specified waiting timeTERMINATED
: The thread has finished its execution
In this recipe, we will implement an example that will create and run 10 threads that would calculate the prime numbers within the first 20,000 numbers.
The example for this recipe has been implemented using the Eclipse IDE. If you use Eclipse or a different IDE, such as NetBeans, open it and create a new Java project.
Follow these steps to implement the example:
- Create a class named
Calculator
that implements the Runnable
interface:
public class Calculator implements Runnable {
- Implement the
run()
method. This method will execute the instructions of the thread we are creating, so this method will calculate the prime numbers within the first 20000
numbers:
@Override
public void run() {
long current = 1L;
long max = 20000L;
long numPrimes = 0L;
System.out.printf("Thread '%s': START\n",
Thread.currentThread().getName());
while (current <= max) {
if (isPrime(current)) {
numPrimes++;
}
current++;
}
System.out.printf("Thread '%s': END. Number of Primes: %d\n",
Thread.currentThread().getName(), numPrimes);
}
- Then, implement the auxiliar
isPrime()
method. This method determines whether a number is a prime number or not:
private boolean isPrime(long number) {
if (number <= 2) {
return true;
}
for (long i = 2; i < number; i++) {
if ((number % i) == 0) {
return false;
}
}
return true;
}
- Now implement the main class of the application. Create a class named
Main
that contains the main()
method:
public class Main {
public static void main(String[] args) {
- First, write some information regarding the values of the maximum, minimum, and default priority of the threads:
System.out.printf("Minimum Priority: %s\n",
Thread.MIN_PRIORITY);
System.out.printf("Normal Priority: %s\n",
Thread.NORM_PRIORITY);
System.out.printf("Maximun Priority: %s\n",
Thread.MAX_PRIORITY);
- Then create 10
Thread
objects to execute 10 Calculator
tasks. Also, create two arrays to store the Thread
objects and their statuses. We will use this information later to check the finalization of the threads. Execute five threads (the even ones) with maximum priority and the other five with minimum priority:
Thread threads[];
Thread.State status[];
threads = new Thread[10];
status = new Thread.State[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(new Calculator());
if ((i % 2) == 0) {
threads[i].setPriority(Thread.MAX_PRIORITY);
} else {
threads[i].setPriority(Thread.MIN_PRIORITY);
}
threads[i].setName("My Thread " + i);
}
- We are going to write information in a text file, so create a try-with-resources statement to manage the file. Inside this block of code, write the status of the threads in the file before you launch them. Then, launch the threads:
try (FileWriter file = new FileWriter(".\\data\\log.txt");
PrintWriter pw = new PrintWriter(file);) {
for (int i = 0; i < 10; i++) {
pw.println("Main : Status of Thread " + i + " : " +
threads[i].getState());
status[i] = threads[i].getState();
}
for (int i = 0; i < 10; i++) {
threads[i].start();
}
- After this, wait for the finalization of the threads. As we will learn in the Waiting for the finalization of a thread recipe of this chapter, we can use the
join()
method to wait for this to happen. In this case, we want to write information about the threads when their statuses change, so we can't use this method. We use this block of code:
boolean finish = false;
while (!finish) {
for (int i = 0; i < 10; i++) {
if (threads[i].getState() != status[i]) {
writeThreadInfo(pw, threads[i], status[i]);
status[i] = threads[i].getState();
}
}
finish = true;
for (int i = 0; i < 10; i++) {
finish = finish && (threads[i].getState() ==
State.TERMINATED);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
- In the previous block of code, we called the
writeThreadInfo()
method to write information about the status of a thread in the file. This is the code for this method:
private static void writeThreadInfo(PrintWriter pw,
Thread thread,
State state) {
pw.printf("Main : Id %d - %s\n", thread.getId(),
thread.getName());
pw.printf("Main : Priority: %d\n", thread.getPriority());
pw.printf("Main : Old State: %s\n", state);
pw.printf("Main : New State: %s\n", thread.getState());
pw.printf("Main : ************************************\n");
}
- Run the program and see how the different threads work in parallel.
The following screenshot shows the console part of the output of the program. We can see that all the threads we have created run in parallel to do their respective jobs:
In this screenshot, you can see how threads are created and how the ones with an even number are executed first, as they have the highest priority, and the others executed later, as they have minimum priority. The following screenshot shows part of the output of the log.txt
file where we write information about the status of the threads:
Every Java program has at least one execution thread. When you run the program, JVM runs the execution thread that calls the main()
method of the program.
When we call the start()
method of a Thread
object, we are creating another execution thread. Our program will have as many execution threads as the number of calls made to the start()
method.
The Thread
class has attributes to store all of the information of a thread. The OS scheduler uses the priority of threads to select the one that uses the CPU at each moment and actualizes the status of every thread according to its situation.
If you don't specify a name for a thread, JVM automatically assigns it one in this format: Thread-XX, where XX is a number. You can't modify the ID or status of a thread. The Thread
class doesn't implement the setId()
and setStatus()
methods as these methods introduce modifications in the code.
A Java program ends when all its threads finish (more specifically, when all its non-daemon threads finish). If the initial thread (the one that executes the main()
method) ends, the rest of the threads will continue with their execution until they finish. If one of the threads uses the System.exit()
instruction to end the execution of the program, all the threads will end their respective execution.
Creating an object of the Thread
class doesn't create a new execution thread. Also, calling the run()
method of a class that implements the Runnable
interface doesn't create a new execution thread. Only when you call the start()
method, a new execution thread is created.
As mentioned in the introduction of this recipe, there is another way of creating a new execution thread. You can implement a class that extends the Thread
class and overrides the run()
method of this class. Then, you can create an object of this class and call the start()
method to have a new execution thread.
You can use the static method currentThread()
of the Thread
class to access the thread object that is running the current object.
You have to take into account that the setPriority()
method can throw an IllegalArgumentException
exception if you try to establish priority that isn't between 1 and 10.
- The Creating threads through a factory recipe of this chapter