Brief Introduction
In Java, a Runnable is an interface that represents a unit of work which can be executed by a thread. It is part of the Java Concurrency framework (java.util.concurrent)
.
Example - A Runnable Task
public class Task implements Runnable {
@Override
public void run() {
// do something meaningful
System.out.println("Hello, World!");
}
}
Example - A Thread running a Runnable Task
The Java Concurrency Framework provides a Thread class which can be used for running a Runnable in a separate thread of execution. This can be done by using the start()
method provided by the Thread class which initiates the execution of the thread's code in a new thread of control. This method is essential for multithreading because it tells the Java Virtual Machine (JVM) to begin executing the run()
method of the underlying Runnable concurrently in a separate thread.
public static void main(String[] args) {
System.out.println("Main method has started.");
Thread t = new Thread(new Task("Charlie"));
t.start();
System.out.println("Main method has finished.");
}
private static class Task implements Runnable {
private final String name;
public Task(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("Task " + name + " has started.");
System.out.println("Hello, World! This is Task : " + name);
System.out.println("Task " + name + " has finished.");
}
}
The output of this program is non deterministic. This is because the operating system and the Java Virtual Machine (JVM) schedule and manage thread execution independently, and the order in which threads (in this case the main thread and the explicitly created task thread) are scheduled to run can vary each time the program is executed.
For instance, one possible output is
Main method has started.
Main method has finished.
Task Charlie has started.
Hello, World! This is Task : Charlie
Task Charlie has finished.
As we are storing the thread reference of the task thread in the main method, we can also wait for its completion before proceeding further in the main method. This can be done by using the join()
method provided by the Thread class which is a blocking call and blocks the caller thread until the callee thread completes execution and dies.
public static void main(String[] args) throws InterruptedException {
System.out.println("Main method has started.");
Thread t = new Thread(new Task("Charlie"));
t.start();
t.join();
System.out.println("Main method has finished.");
}
private static class Task implements Runnable {
private final String name;
public Task(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("Task " + name + " has started.");
System.out.println("Hello, World! This is Task : " + name);
System.out.println("Task " + name + " has finished.");
}
}
Note how we've also added the InterruptedException
to the main method's signature. That is because the join()
method of the Thread class throws this checked exception. More about this in another post, but in summary When you call the join() method on a thread (let's call it "Thread 1") from another thread (let's call it "Thread 2"), Thread 2 will wait until Thread 1 completes its execution. In other words, Thread 2 will be blocked until Thread 1 finishes. If, while Thread 2 is waiting, another thread (let's call it "Thread 3") interrupts Thread 2 (e.g., by invoking Thread.interrupt() on Thread 2), Thread 2 will throw an InterruptedException.
The output of this program in this case is deterministic.
Main method has started.
Task Charlie has started.
Hello, World! This is Task : Charlie
Task Charlie has finished.
Main method has finished.
Miscellaneous Example
public static void main(String[] args) {
System.out.println("Main method has started.");
for (int i=0; i<5; ++i) {
new Task();
}
System.out.println("Main method has finished.");
}
private static class Task extends Thread {
private static int numTasks = 0;
private final int id;
public Task() {
this.id = ++numTasks;
this.start();
}
@Override
public void run() {
System.out.println("Task " + id + " has started.");
try {
TimeUnit.MILLISECONDS.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Hello, World! This is Task : " + id);
try {
TimeUnit.MILLISECONDS.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Task " + id + " has finished.");
}
}
The output for this is again non-deterministic. One possible output is
Main method has started.
Main method has finished.
Task 5 has started.
Task 2 has started.
Task 4 has started.
Task 1 has started.
Task 3 has started.
Hello, World! This is Task : 4
Hello, World! This is Task : 5
Hello, World! This is Task : 1
Hello, World! This is Task : 2
Hello, World! This is Task : 3
Task 2 has finished.
Task 4 has finished.
Task 5 has finished.
Task 1 has finished.
Task 3 has finished.
Top comments (0)