Each thread is associated with an instance of the class Thread
, which provides the code that will run in the thread.
We can do that in two ways, that is,
Runnable
interface, orThread
class.Runnable
A class must implement Runnable
for its instances to to be executed by a thread.
import java.util.Random;
public class PrintTask implements Runnable {
private final String name;
private final int sleepTime;
public PrintTask(String name) {
this.name = name;
this.sleepTime = (new Random()).nextInt(5000);
}
@Override
public void run() {
System.out.printf("%s: I'm going to sleep for %d milliseconds.%n", name, sleepTime);
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
System.out.printf(
"%s: I woke up and finished executing.%n", name);
}
}
Runnable
with an ExecutorService
Executors provide methods to create thread pools and run our tasks.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorMain {
public static void main(String[] args) throws InterruptedException {
System.out.printf("%s: Starting executor%n", Thread.currentThread().getName());
PrintTask task1 = new PrintTask("Task1");
PrintTask task2 = new PrintTask("Task2");
PrintTask task3 = new PrintTask("Task3");
ExecutorService executor = Executors.newCachedThreadPool();
executor.execute(task1);
executor.execute(task2);
executor.execute(task3);
executor.shutdown();
System.out.printf("%s: Executor terminated%n", Thread.currentThread().getName());
}
}
By running this the program once, we get the following output.
main: Starting executor
pool-1-thread-1: Task1 going to sleep for 621 milliseconds
main: Executor terminated
pool-1-thread-3: Task3 going to sleep for 4671 milliseconds
pool-1-thread-2: Task2 going to sleep for 34 milliseconds
pool-1-thread-2: Task2 woke up and finished executing
pool-1-thread-1: Task1 woke up and finished executing
pool-1-thread-3: Task3 woke up and finished executing
If we run it again, we get the following.
main: Starting executor
pool-1-thread-1: Task1 going to sleep for 3371 milliseconds
pool-1-thread-2: Task2 going to sleep for 2174 milliseconds
pool-1-thread-3: Task3 going to sleep for 774 milliseconds
main: Executor terminated
pool-1-thread-3: Task3 woke up and finished executing
pool-1-thread-2: Task2 woke up and finished executing
pool-1-thread-1: Task1 woke up and finished executing
Why are the outputs different?
Alternatively, we can create and start threads ourselves.
public class ThreadMain {
public static void main(String[] args) {
System.out.printf("%s: Starting ThreadMain%n", Thread.currentThread().getName());
PrintTask task0 = new PrintTask("Task0");
PrintTask task1 = new PrintTask("Task1");
PrintTask task2 = new PrintTask("Task2");
Thread thread0 = new Thread(task0);
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);
thread0.start();
thread1.start();
thread2.start();
System.out.printf("%s: ThreadMain terminated%n", Thread.currentThread().getName());
}
}
Thread
The second approach consists of extending the Thread
class and overriding the run()
method.
import java.util.Random;
class PrintThread extends Thread {
private int sleepTime;
public PrintThread(String name) {
super(name);
Random rand = new Random();
this.sleepTime = rand.nextInt(5000);
}
@Override
public void run() {
System.out.printf("%s: going to sleep for %d milliseconds%n", this.getName(), sleepTime);
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
System.out.printf("%s: woke up and finished executing%n", this.getName());
}
}
Thread
Then, we running by invoking start()
public class ExtendThread {
public static void main(String[] args) {
System.out.printf("%s: Starting ExtendThread%n", Thread.currentThread().getName());
PrintThread thread0 = new PrintThread("Thread0");
PrintThread thread1 = new PrintThread("Thread1");
PrintThread thread2 = new PrintThread("Thread2");
thread0.start();
thread1.start();
thread2.start();
System.out.printf("%s: ThreadMain terminated%n", Thread.currentThread().getName());
}
}
Implementing the Runnable
interface
Extending the Thread
class
Remember inheritance?
When creating a thread we usually want to HAVE the thread functionality and have some other functionality as well.
The following is a general guideline.
From now on, we will focus on implementing the Runnable
interface.
What is the difference between the first and the second implementations?
In both cases, task0
, task1
, and task2
are instances of a class that implements the Runnable
interface.
public static void main(String[] args) {
System.out.printf("%s: Starting ThreadMain%n",
Thread.currentThread().getName());
PrintTask task0 = new PrintTask("Task0");
PrintTask task1 = new PrintTask("Task1");
PrintTask task2 = new PrintTask("Task2");
task0.run();
task1.run();
task2.run();
System.out.printf("%s: ThreadMain terminated%n",
Thread.currentThread().getName());
}
public static void main(String[] args) {
System.out.printf("%s: Starting ThreadMain%n",
Thread.currentThread().getName());
PrintTask task0 = new PrintTask("Task0");
PrintTask task1 = new PrintTask("Task1");
PrintTask task2 = new PrintTask("Task2");
Thread thread0 = new Thread(task0);
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);
thread0.start();
thread1.start();
thread2.start();
System.out.printf("%s: ThreadMain terminated%n",
Thread.currentThread().getName());
}
When we call task0.run()
we are executing the method in the current thread.
When we call thread0.start()
, the JVM starts a new thread and calls the run()
method of task0
internally within that new thread.
The same thing happens for task1
, task2
, thread1
, and thread2
.
Threads can be given a custom name:
public class NamedThread {
public static void main(String[] args) {
String mainThreadName = Thread.currentThread().getName();
System.out.printf("%s: Starting ThreadMain%n", mainThreadName);
PrintTask task0 = new PrintTask("Task0");
Thread thread0 = new Thread(task0, "my-thread");
System.out.printf("%s: starting%n", thread0.getName());
thread0.start();
System.out.printf("%s: ThreadMain terminated%n", mainThreadName);
}
}
Each thread has a priority.
In most cases, the thread scheduler schedules the threads according to their priority.
Thread th1 = new Thread(t1, "HI");
th1.setPriority(10);
Run this code several times and check the output.
public class PrioritizedThreads {
public static void main(String[] args) {
class T1 implements Runnable {
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 30; i++)
System.out.printf("%s: %d%n", name, i);
System.out.printf("%s: finalized!%n", name);
}
}
Thread th1 = new Thread(new T1(), "HI");
th1.setPriority(10);
Thread th2 = new Thread(new T1(), "MID");
th1.setPriority(5);
Thread th3 = new Thread(new T1(), "LOW");
th1.setPriority(1);
th1.start();
th2.start();
th3.start();
}
}
Which states do our threads go through?
start()
.start()
.synchronized
block/method.wait()
and before receiving a notify()
.sleep()
.run()
method exits.Use the getState()
method on a Thread
object.
public class ThreadStatesMain {
public static void main(String[] args) throws InterruptedException {
System.out.printf("%s: Starting ThreadStatesMain%n", Thread.currentThread().getName());
PrintTask task0 = new PrintTask("Task0");
PrintTask task1 = new PrintTask("Task1");
PrintTask task2 = new PrintTask("Task2");
Thread thread0 = new Thread(task0);
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);
System.out.printf("%s: State: %s%n", thread0.getName(), thread0.getState());
thread0.start();
thread1.start();
thread2.start();
System.out.printf("%s: State: %s%n", thread0.getName(), thread0.getState());
thread0.join();
System.out.printf("%s: State: %s%n", thread0.getName(), thread0.getState());
System.out.printf("%s: ThreadStatesMain terminated%n", Thread.currentThread().getName());
}
}
Thread.sleep
causes the current thread to suspend execution for a specified period.
It is a means of making processor time available to the other threads
sleep
is available in two versions, that is,
Thread.sleep(long millis)
Thread.sleep(long millis, long nanos).
Here is a demonstration of its use.
public class SleepMessage {
public static void main(String args[]) throws InterruptedException {
String message[] = {
"message 1, ",
"message 2, ",
"message 3, ",
"message 4, ",
"message 5."
};
for (int i = 0; i < message.length; i++) {
System.out.println(message[i]);
Thread.sleep(2000);
}
}
}
Consider the code below.
class JoinMe implements Runnable {
@Override
public void run() {
System.out.println("Started thread JoinMe");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
System.out.println("Ended thread JoinMe");
}
public static void main(String[] args) throws InterruptedException {
JoinMe joinMe = new JoinMe();
Thread tt1 = new Thread(joinMe);
tt1.start();
System.out.println("Main thread continues to run.");
}
}
What will the output of this code likely be?
Main thread continues to run.
Started thread T1
Ended thread T1
The join()
method allows one thread to wait for the completion of another.
class JoinMe implements Runnable {
@Override
public void run() {
System.out.println("Started thread JoinMe");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
System.out.println("Ended thread JoinMe");
}
public static void main(String[] args) throws InterruptedException {
JoinMe joinMe = new JoinMe();
Thread tt1 = new Thread(joinMe);
tt1.start();
tt1.join();
System.out.println("Main thread continues to run.");
}
}
What will the output of this code be?
Started thread T1
Ended thread T1
Main thread continues to run.
We can use join()
for multiple threads.
public class JoinThreads {
public static void main(String[] args) throws InterruptedException {
System.out.printf("%s: Starting ThreadMain%n", Thread.currentThread().getName());
PrintTask task0 = new PrintTask("Task0");
PrintTask task1 = new PrintTask("Task1");
PrintTask task2 = new PrintTask("Task2");
Thread thread0 = new Thread(task0);
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);
thread0.start();
thread1.start();
thread2.start();
thread0.join();
thread1.join();
thread2.join();
System.out.printf("%s: ThreadMain terminated%n", Thread.currentThread().getName());
}
}
ExecutorService
If we are running tasks using an ExecutorService
, we can use awaitTermination()
.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ExecutorWaitMain {
public static void main(String[] args) throws InterruptedException {
System.out.printf("%s: Starting executor%n", Thread.currentThread().getName());
PrintTask task1 = new PrintTask("Task1");
PrintTask task2 = new PrintTask("Task2");
PrintTask task3 = new PrintTask("Task3");
ExecutorService executor = Executors.newCachedThreadPool();
executor.execute(task1);
executor.execute(task2);
executor.execute(task3);
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
System.out.printf("%s: Executor terminated%n", Thread.currentThread().getName());
}
}
In this case, there is no guarantee that the threads will terminate in a given order.
Interrupting a thread stops what it is doing to do something else.
A thread interrupts another by invoking the interrupt() method on it.
interrupt()
sets this flag.A thread checks for its interrupt status by invoking the static method Thread.interrupted()
.
The non-static isInterrupted()
method is used by one thread to query the interrupt status of another.
For the interrupt mechanism to work correctly, the interrupted thread must support its own interruption.
There are 2 options.
InterruptedException
in your run method.
This requires using a method that throws InterruptedException.
Thread.interrupted
.
class NonInterruptibleTask implements Runnable {
@Override
public void run() {
System.out.printf("Non-Interruptible task: running...%n");
for (int i = 0; i < 100; i++) {
System.out.printf("Non-Interruptible task: %d%n", i);
}
System.out.printf("Non-Interruptible task: terminated%n");
}
}
public class CantInterrupt {
public static void main(String[] args) throws InterruptedException {
String threadName = Thread.currentThread().getName();
System.out.printf("%s: Starting...%n", threadName);
NonInterruptibleTask t = new NonInterruptibleTask();
Thread th = new Thread(t);
th.start();
System.out.printf("%s: Interrupting child thread.%n", threadName);
th.interrupt();
System.out.printf("%s: Terminated.%n", threadName);
}
}
interrupted()
class InterruptibleTask implements Runnable {
@Override
public void run() {
System.out.printf("Interruptible task: running...%n");
for (int i = 0; i < 10000; i++) {
System.out.printf("Interruptible task: %d%n", i);
if(Thread.interrupted()){
System.out.println("Interruptible task: I was told to stop");
return;
}
}
System.out.printf("Interruptible task: terminated%n");
}
}
public class CanInterrupt {
public static void main(String[] args) throws InterruptedException {
String threadName = Thread.currentThread().getName();
System.out.printf("%s: Starting...%n", threadName);
InterruptibleTask t = new InterruptibleTask();
Thread th = new Thread(t);
th.start();
Thread.sleep(100);
System.out.printf("%s: Interrupting child thread.%n", threadName);
th.interrupt();
System.out.printf("%s: Terminated.%n", threadName);
}
}
InterruptedException
class MyTask implements Runnable {
String name;
boolean canGoBackToSleep;
public MyTask(String name, boolean canGoBackToSleep) {
this.name = name;
this.canGoBackToSleep = canGoBackToSleep;
}
@Override
public void run() {
while (true) {
try {
System.out.printf("%s: I'm going to sleep...%n", name);
Thread.sleep(5000);
System.out.printf("%s: I finished sleeping. :)%n", name);
return;
} catch (InterruptedException e) {
System.out.printf("%s: My sleep has been interrupted. :(%n", name);
if (!this.canGoBackToSleep)
return;
}
}
}
}
public class Interruptor {
public static void main(String[] args) throws InterruptedException {
System.out.printf("%s: Starting...%n", Thread.currentThread().getName());
MyTask t0 = new MyTask("Task0", true);
Thread th0 = new Thread(t0);
th0.start();
Thread.sleep(2000);
th0.interrupt();
MyTask t1 = new MyTask("Task1", false);
Thread th1 = new Thread(t1);
th1.start();
Thread.sleep(2000);
th1.interrupt();
System.out.printf("%s: Terminated.%n", Thread.currentThread().getName());
}
}
Part of the material has been taken from the following sources. The usage of the referenced copyrighted work is in line with fair use since it is for nonprofit educational purposes.