In this Java concurrency tutorial, I will try to guide you how to create a thread, then perform basic thread operations such as start, pause, interrupt & join. You will understand exactly how threads work at the lowest level of Java.
1. How to create a thread in Java
There are 2 ways to do it, either by extending (deriving the Thread class) or by implementing the Runnable interface. Both are part of the java.lang package, so you don't need to bother importing them. Put the code needed to be executed in a separate thread inside the run() method, which is the result of overriding Thread/Runnable. Meanwhile, the start() method is used to run the Thread with status (alive).
Here is a show between the main thread (belonging to the java system) and the additional threads I created.
public class ThreadExample1 extends Thread {
public void run() {
System.out.println("My name is: " + getName());
}
public static void main(String[] args) {
ThreadExample1 t1 = new ThreadExample1();
t1.start();
System.out.println("My name is: " + Thread.currentThread().getName());
}
}
I will try to explain how the above code works. Note the ThreadExample1 class that extends the Thread class and overrides the run() method. In the run() method, I try to identify the created thread by printing it to the console / terminal, so that I know what name the system uses for my created thread. Actually when the program is run, the main thread is the first to be executed, so the main thread is the one that triggers t1.start() so I can find out the names of the threads by calling the .getName() method
The static method currentThread() will return the value of the Thread associated with it. You can do this in other associations, such as Runnable() perhaps. We can see the output.
My name is: Thread-0
My name is: main
Now we know that there are 2 threads there:
- Thread-0: is the name of the thread I created.
- main: is the name of the main thread that automatically runs as the soul of the Java program itself.
Thread-0 ends immediately after the run() method completes executing the commands contained within it, and the main thread ends in the same way.
The interesting thing here is that if you run the program more than once, you will see that sometimes Thread-0 is executed first, sometimes the main thread is first. We can identify all of this by printing the thread names. Okay, let's just call it random. That is, the experiment above shows that there is no guarantee that the first thread to execute will always be the first to run, because they actually run concurrently. You should keep this behavior in mind in the context of multi-threading.
Now, I use the 2nd way which is implementing Runnable interface. The code below is my definition of ThreadExample2 class by overriding the run() method as a function of Runnable.
public class ThreadExample2 implements Runnable {
public void run() {
System.out.println("My name is: " + Thread.currentThread().getName());
}
public static void main(String[] args) {
Runnable task = new ThreadExample2();
Thread t2 = new Thread(task);
t2.start();
System.out.println("My name is: " + Thread.currentThread().getName());
}
}
You can notice that there is a slight difference compared to the previous code, where an object of type Runnable is created and passed to the Thread object(t2) constructor. Runnable objects can be represented as separate tasks from the thread that executes them.
Both programs behave the same, so what are the pros and cons of each method?
- Extending Thread class, can be used for simple cases, because Java does not allow multiple class inheritance if you want to extend its functionality.
- Implementing a Runnable interface is the most flexible way to extend the functionality of your class, because Java allows implementing more than one interface.
You also need to remember that a thread that ends after the run() method is put into a dead state and cannot be restarted. You can never restart a dead thread.
You can also give a name to the thread you create, either via the Thread class constructor or via setName(), for example.
Thread t1 = new Thread("First Thread");
Thread t2 = new Thread();
t2.setName("Second Thread");
2. How to pause a thread
In addition to running, you can also temporarily stop the thread that is currently running, by invoking the static method sleep(miliseconds) from the Thread class, then it will sleep for the time you have specified (in seconds), for example.
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
// code to resume or terminate...
}
The above code will sleep the thread for 2 seconds (or 2000 milliseconds), after which it will run normally again.
InterruptedException will check the exception if something is not met, so you have to handle this, whether it needs to be thrown to a special class, a special UI, a special log, or whatever you want, the important thing is not to let the system handle this because it is not reliable in terms of intuition, it will openly display the error that occurred.
I give an example of how the NumberPrint program updates 5 numbers every 2 seconds.
public class NumberPrint implements Runnable {
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(i);
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
System.out.println("I'm interrupted");
}
}
}
public static void main(String[] args) {
Runnable task = new NumberPrint();
Thread thread = new Thread(task);
thread.start();
}
}
Note: you cannot pause a thread from another thread. Only the thread itself can pause its job execution. In addition, there is no guarantee that every thread that sleeps is always really sleeping, because each thread can interrupt each other, so if it is in a weak condition (sleep), then the system can delegate its memory to another thread that is more active. Regarding this, we will discuss further on another occasion.
3. How to interrupt a thread
"Someone interrupts another person's conversation" , perhaps this is an illustration that is easy for you to understand when performing an Interrupt operation, just as participants in a conversation can interrupt each other, so can fellow threads interrupt/pause each other, this can be done by involving the stop and resume functions, both of which can be executed from other threads.
Following is an example of an Interrupt statement for thread t1.
t1.interrupt();
Another thing you need to pay attention to is when interrupting a sleeping thread, it will trigger an interruptedException, then the decision is in your hands, whether to wake up the sleeping thread or cancel, or do another job. Whatever decision you take, please define it in the catch{..} code block.
Here we provide a simple example with a scenario where thread t1 prints a message every 2 seconds, and the main thread interrupts it after 5 seconds.
public class ThreadInterruptExample implements Runnable {
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println("This is message #" + i);
try {
Thread.sleep(2000);
continue;
} catch (InterruptedException ex) {
System.out.println("I'm resumed");
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new ThreadInterruptExample());
t1.start();
try {
Thread.sleep(5000);
t1.interrupt();
} catch (InterruptedException ex) {
// do nothing
}
}
}
As seen in the catch block in the run() method, it continues looping when the thread is interrupted. That is, the thread is asked to continue working while it is sleeping.
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
System.out.println("I'm resumed");
continue;
}
But if you want it to stop, then simply change the statement at the end of the catch block with a return, like this.
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
System.out.println("I'm about to stop");
return;
}
The return statement above is intended to end the thread's work, so that it goes to dead state.
What if terminating a sleeping thread? is there no need to handle InterruptedException?
In such cases, you need to check the interrupt status of the current thread, using one of the following methods of the Thread class:
- interrupted(), this static method returns true if the current thread has been interrupted, or false otherwise. Note that this method also clears the interrupt status, meaning that if it returns true, the interrupt status is set to false, and vice versa.
- isInterrupted(), this static method has the same principle, returning true or false, but does not change the current thread status.
Now, we can modify the above code to provide an example of Thread Interruption with status checking.
public class ThreadInterruptExample implements Runnable {
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println("This is message #" + i);
if (Thread.interrupted()) {
System.out.println("I'm about to stop");
return;
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new ThreadInterruptExample());
t1.start();
try {
Thread.sleep(5000);
t1.interrupt();
} catch (InterruptedException ex) {
// do nothing
}
}
}
Now the above version is not the same as before, because t1 terminates very quickly, since it is not sleeping and the print statement is also executed faster. So, this example is just to show how to use it. In practice, this kind of interrupt status checking should be implemented for long-running operations like I/O, networking, database, etc.
Remember!, that when an InterruptedException is thrown, the interrupt status is cleared.
If you look at the Thread class in the Javadocs, you will see these 4 methods:
destroy() - stop() - suspend() - resume()
But all of these methods are obsolete, meaning you shouldn't use them. Let's use the interrupt mechanism I've explained so far.
4. How to Make a Thread Wait for Another Thread (Join)
This scenario is called join, useful when you want the current thread (the thread that is currently running) to wait for another thread to finish first. Then the current thread will run again, for example.
t1.join();
The above statement causes the current thread to wait for thread t1 to finish first. Below we give an example of the main thread as the current thread waiting for thread t1.
public class ThreadJoinExample implements Runnable {
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println("This is message #" + i);
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
System.out.println("I'm about to stop");
return;
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new ThreadJoinExample());
t1.start();
try {
t1.join();
} catch (InterruptedException ex) {
// do nothing
}
System.out.println("I'm " + Thread.currentThread().getName());
}
}
In the above program, the main thread (current thread) will always be in terminates condition until thread t1 is finished. Therefore, the message "I'm main" is always printed last.
This is message #1
This is message #2
This is message #3
This is message #4
This is message #5
This is message #6
This is message #7
This is message #8
This is message #9
This is message #10
I'm main
Note: the join() method will throw an InterruptedException, when the current thread is interrupted, so you need to handle this with a catch exception.
There are 2 overloads of the join() method:
- join(milliseconds)
- join(milliseconds, nanoseconds)
Both methods cause the current thread to wait for a specified amount of time. That is, it is a tolerance for waiting time for other threads, if it turns out that the other thread has not finished during that time, then the current thread ignores the other thread and runs as usual.
You can also join multiple threads with the current thread, like this.
t1.join();
t2.join();
t3.join();
The above case will make the current thread wait for the three threads to finish first.
That's the fundamentals of using threads in Java, hopefully it's useful, don't forget to share, and happy learning!