Java Concurrency, Part 2: Manipulating Threads
Learn more about manipulating threads in your Java projects.
Join the DZone community and get the full member experience.
Join For FreeAfter seeing how to create Threads, this article will explore what we can do to manipulate Threads
in our Java applications.
Please keep in mind all code demonstrated in this article has been tested and debugged in Java 12.
So without further ado, let's get started.
You may also like: A Java 8 Streams Cookbook
When we have Thread
s, we have several operations:
- Make the current
Thread
sleeping during x milliseconds - Wait for another thread to complete
- Manage the priorities of
Thread
s and pause a thread to give another thread the opportunity to run - Interrupt a thread
We'll now see how to do all these things.
First and easier, we can make a thread sleeping for a certain number of milliseconds. To do that, the Thread
class has a method of sleep (long millis). But this method is static, so you can only make the current Thread
sleeping. You cannot choose the Thread
you want to make sleeping, your only choice is the current Thread, so we have:
Thread.sleep(1000);
This makes the current Thread
sleep during 1000 milliseconds (1 second). But, you have to catch an exception, InterruptedException
. This exception occurs if the sleeping thread is interrupted. So you have to do that:
try {
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
But this is not a good way to manage the InterruptedException
. We'll see in one moment how to deal with this exception.
If you want more precision, you can use the overloaded version of sleep that takes the number of milliseconds plus a certain number of nanoseconds to sleep. The precision of this sleep depends on the system timers and clocks.
For example, if you want to sleep 1000 milliseconds and 1000 nanoseconds (1 microsecond), you can do so like this:
try {
Thread.sleep(1000, 1000);
} catch (InterruptedException e){
e.printStackTrace();
}
Here is a little example to test that:
public class SleepThread {
public static void main(String[] args) {
System.out.println("Current time millis : " + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Current time millis : " + System.currentTimeMillis());
System.out.println("Nano time : " + System.nanoTime());
try {
Thread.sleep(2, 5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Nano time : " + System.nanoTime());
}
}
In my computer, this produces the following result:
Current time millis : 1273959308480
Current time millis : 1273959309480
Nano time : 5878165216075
Nano time : 5878166730976
You can see that the sleep of milliseconds is very precise, but with nanoseconds, the result can vary a lot. And of course, the result depends of your computer, your operating system, and your configuration.
Another thing you can do with Thread
s is wait for another thread to die. For example, you can create five thread to compute the sub result and wait for these 5 threads to finish to compute the final results based on the results of the five threads. To do that, you can use the join()
method of the Thread
class. This method is not static, so you can use it on any thread to wait for it to die. Like sleep()
, this method throws InterruptedException
when the thread is interrupted waiting for another thread. So to wait on thread2, you just have to do that:
try {
thread2.join();
} catch (InterruptedException e){
e.printStackTrace();
}
That will make the current Thread
waiting for thread2
die. You can also add a timeout in millis, or millis + nanos, with the overloaded versions of join(), join(long millis)
and join(long millis, int nanos)
. Here is a little example that shows all of that stuff:
public class JoinThread {
public static void main(String[] args) {
Thread thread2 = new Thread(new WaitRunnable());
Thread thread3 = new Thread(new WaitRunnable());
System.out.println("Current time millis : " + System.currentTimeMillis());
thread2.start();
try {
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Current time millis : " + System.currentTimeMillis());
thread3.start();
try {
thread3.join(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Current time millis : " + System.currentTimeMillis());
}
private static class WaitRunnable implements Runnable {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
This will produce the following result on my computer:
Current time millis : 1274015478535
Current time millis : 1274015483538
Current time millis : 1274015484538
You can see that the first join()
wait 5 seconds for the other thread, and when we set a timeout, we wait only 1 second and return from the join method.
When working with Thread
s, it's also possible to change the priority of a Thread
. In the Java Virtual Machine, the Thread
scheduler, use priority-based scheduling. So if a Thread
entered in Runnable
state with a higher priority than the running Thread
, the new Thread
will run and the current running thread will return to Runnable
state and waits for its turn. But this behavior is not guaranteed and is completely depending on the virtual machine you are working on. So, do not rely on thread priorities, just use them to improve the efficiency of your program.
Normally, the priority range of Thread
s is an integer from 0 to 10, but some virtual machines have lower or higher ranges. To know the range of priority, you can use constants of the Thread
class :
public class ThreadPriorityRange {
public static void main(String[] args) {
System.out.println("Minimal priority : " + Thread.MIN_PRIORITY);
System.out.println("Maximal priority : " + Thread.MAX_PRIORITY);
System.out.println("Norm priority : " + Thread.NORM_PRIORITY);
}
}
On my computer, I have the most current values:
Minimal priority : 1
Maximal priority : 10
Norm priority : 5
To set the priority of a Thread
, you can use the setPriority(int priority)
method of the Thread
class. If you enter a value greater than the maximal priority, the maximal value will be used. If you don't specify a priority, the used priority will be the priority of the current Thread
.
Another way to works with priority is the yield()
method. This method is static, so this works on the current Thread
. The purpose of this method is to make the Thread
going to Runnable
again and to give the opportunity to other threads to get their turn. But in practice, the behavior of this method is not guaranteed. It can be implemented as a no-op on certain systems. It's not easy to test that in practice, because the results can truly depend on your computer, virtual machine, and operating system. It's a good thing to not use the priorities of Thread
=s in practice.
The last thing you can do with a Thread
is to interrupt it. In Java, you have no way to force a Thread
to stop, if the Thread is not well-done, it can continue its execution infinitely. But you can interrupt a Thread
with the interrupt()
method. This method interrupts the thread; if the thread is sleeping or joining another Thread
, an InterruptedException
is thrown. You have to know that if the thread was sleeping or joining, the interrupted status of the Thread
will be cleared. Namely, the method isInterrupted()
will return false. A little example to demonstrate that is shown below:
public class InterruptThread {
public static void main(String[] args) {
Thread thread1 = new Thread(new WaitRunnable());
thread1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread1.interrupt();
}
private static class WaitRunnable implements Runnable {
@Override
public void run() {
System.out.println("Current time millis : " + System.currentTimeMillis());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println("The thread has been interrupted");
System.out.println("The thread is interrupted : " + Thread.currentThread().isInterrupted());
}
System.out.println("Current time millis : " + System.currentTimeMillis());
}
}
}
That will produce this kind of result:
Current time millis : 1274017633151
The thread has been interrupted
The thread is interrupted : false
Current time millis : 1274017634151
You can see that after one second, the second thread is interrupted and that the interrupted status has been set to false. If you are not sleeping, but making a lot of heavy actions, you can test for interrupt like that to make your thread correctly interrupts:
public class InterruptableRunnable implements Runnable {
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()){
//Heavy operation
}
}
}
Now that you know how to interrupt a thread, you can imagine simplint caching the InterruptedException
is not enough to make your thread "interrupt safe". Imagine that your thread has something like this:
public class UglyRunnable implements Runnable {
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()){
//Heavy operation
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//Other operation
}
}
}
And now, another Thread want to interrupt your thread while your thread is sleeping. The sleep will be interrupted, but the interrupted status will be cleared so the loop will continue. A solution to make a better thread is to interrupt again the thread after an InterruptedException
:
public class BetterRunnable implements Runnable {
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()){
//Heavy operation
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
//Other operation
}
}
}
With that code, the interrupted status will be restored and the loop will be stopped after interrupt. Depending on your code, you can also add a continue statement after the interrupt()
to not make operations after interrupt. In some cases, you'll also need to make several if statements to test the interrupted status to do or not to do some operations. Keep in mind that
So, we've now covered the main operations you can do on threads. I hope you found this article interesting!
You can download the sources of this article here: Java Concurrency Sources - Part 2
In the next article on Java concurrency, we'll see how to synchronize code to make it Thread-safe.
Stay tuned!
Further Reading
Published at DZone with permission of Baptiste Wicht, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments