Learn Java Programming
8.0 Thread programming
The following are the objectives of this module:
- Why do we need Threads
- Understand difference between process and threads
- Java’s contract for a Thread – the run method
- How to set the name for a thread – how to start a thread
- How to have a thread running as long as the parent process
- How to stop a thread
- How to have communication between threads
- How to have a resource be accessed only by one thread at a time
Threads are used to achieve concurrency within our programs.
What is concurrency?
By concurrency, we refer to the ability of the process to do more than one thing at the same time.
We know a processor is the central part of a computer. In a computer processor, more than one process are executed.
We have a process monitoring and responding to keyboard input, another process for the mouse operations, yet another process waiting for network connections and a process to manage the printer operations. The list goes on. In short, inside the computer there are a number of processes and each process must execute concurrently.
[If one process is executing, it must not stop the other process from doing its work – this is called concurrency]
We also know that each process has its own memory. Memory for each process is allocated on the Heap. Heap memory is dynamically allocated memory – this memory is held as long as the program is being executed.
How do we achieve concurrency?
Let us think of a web server.
To the Operating System, it is yet another process with a unique id and managed by the operating system and competing with other processes for finite resources [ Remember that the memory on the server is fixed – so processes compete for the finite resources]
Let us say, there is a request to the web server. The web server performs some service, creates a response and sends it to the requester.
Let say there are more than one requester at the same time. If the web server, had to perform more than one service, at the same time, the only way it be able to do it is to have another duplicate process. Well so far, so good. What if another request came in at the same time – we would need to have a third process to serve that request. This keeps going on with a new process being created for each new request.
Concurrency can thus be achieved by having multiple processes serving each request.
But there are two problems with this approach.
- We cannot add such processes indefinitely. We can continue adding new similar process, but only as permitted by the capacity of the processor in the server machine. There is a physical limit on the number of processes that can be created on a given processor.
- Each process has its own distinct allocated memory on the heap. Processes cannot share the data between them.
Threads were born to solve this problem of concurrency. Threads are an alternative solution to achieve concurrency.
Threads are light weight process. It is a process within a process.
Thread does not have a process id of its own. Threads can share memory and resources among other similar threads. Threads solve the problem of concurrency very neatly.
A single web server process – can have multiple threads – each capable of surviving one request from a client. (In fact, we could have a constant number of thread is a pool, reusing them as and when needed.)
Threads thus spell concurrency.
Creating Threads
There are two ways to create threads:
- Extend java.lang.Thread class (or)
- Implement the java lang.Runable interface
Either way we choose to create a thread, the contract for a thread is to have the implementation for the method
public void run();
The public void run() method:
- The method public void run() is defined in the java.lang.Runnable interface.
- It is also defined as an abstract method in the java lang.Thread class
This method defines the behavior of the Thread object – when a new thread is created – and started, it will execute the public void run() {… } method and terminate.
Starting a thread
We need to call the start() method on the Thread class to actually start a thread.
If an class, implements the Runnable interface, then we can create a new thread like this.
Thread t = new Thread (this);
t.start();
If the class extends java.lang.Thread, then cast the object to a Thread type and invoke the run method:
A Thread class which implements the java.lang.Runnable interface is shown below – Note how we create the Thread in the main() method by passing the Runnable reference and then start the thread.
public class ThreadDemo implements Runnable {
public void run() {
System.out.println(
"Thread printing this line before exiting...");
}
}
public class TestThread {
public static void main(String[] args) {
Thread t = new Thread( new ThreadDemo());
t.start();
}
}
A Thread class which extends java.lang.Thread is shown below – Note how we create the Thread in the main() method
public class ThreadDemo2 extends Thread {
public void run() {
System.out.println(
"Thread printing this line before exiting...");
}
}
public class TestThread2 {
public static void main(String[] args) {
ThreadDemo2 t = new ThreadDemo2();
t.start();
}
}
In either case, it would serve to remind that When the thread is started, the public void run() method gets invoked – and the thread terminates after the method is completed.
Thread names
In our programs, we would have multiple threads executing at the same time. In order to identify thread that is executing, it is possible to set a name for a thread.
The thread class has a method called setName();
Thread t = new Thread (this);
t.setName(“T1”);
t.start();
Running a thread continuously
We saw that when the thread is created and started, it invokes the run() method – and when the run method completes, the thread will terminate.
Now let us say that we want the thread to run continuously – like in the case of the web server, where we want the thread to be up and running all the time.
It is as simple as having a while(true) { … } inside the run() method – now the thread is running for-ever.
Caution:
Do not use the while(true) loop without any sleep() call in your program – you will end up taking all the system resources and bring your computer to a griding halt — If you want to try this out, run the following program and see for yourself (You may have to reboot your system – please proceed with caution)
public class BadThreadDesign extends Thread {
public BadThreadDesign() {}
public void run() {
long i = 0;
while(true) {
System.out.println("Printing ... " + i);
i++;
}
}
}
public class BadThreadDesignDemo {
public static void main(String[] args) {
for (int i=0; i < 10; i++) {
BadThreadDesign btd = new BadThreadDesign();
btd.setName("A" + i);
btd.start();
}
}
}
It is always advisable to have a sleep() method inside the while(true) loop – so that we can yield control from our program to the operating system, for it to do its chores. If we do not use the sleep() method, then we never give an opportunity to the operating system and steal its cycles – and hence the system becomes very unresponsive, and you may need to kill the process (or restart your system depending on your target machine)
public class GoodThreadDesign extends Thread {
public GoodThreadDesign() {
}
public void run() {
long i = 0;
while(true) {
System.out.println("Printing ... " + i);
i++;
Thread.sleep(2000); //-- sleep for at least two seconds
}
}
}
public class GoodThreadDesignDemo {
public static void main(String[] args) {
for (int i=0; i < 10; i++) {
GoodThreadDesign gtd = new GoodThreadDesign();
gtd.setName("B" + i);
gtd.start();
}
}
}
Stopping a running thread
It is always advisable to have a entry inside the run() method that will also us to exit a continuous loop. For example, instead of the while(true) loop we could have a while(boolean variable) loop.
Now the loop executes only as long as the boolean variable is true.
Now provide a separate public method that can control the state of this variable
For example:
boolean keepRunning = true;
public void run() {
. . .
while (keepRunning) {
. . .
Thread.sleep(2000);
}
}
public void stop() {
this.keepRunning = false;
}
Whenever, we want to stop the threads, now we will have to call the stop() method on the Thread object – this will set the boolean condition in the forever loop on the run method to false – the run method will complete and the Thread will now terminate gracefully.
public class GoodThreadDesign extends Thread {
private boolean keepRunning = true;
public GoodThreadDesign() {}
public void run() {
long i = 0;
while(keepRunning) {
System.out.println("Printing ... " + i);
i++;
Thread.sleep(2000); //-- wait for atleast two seconds
}
}
public void stop() {
this.keepRunning = false;
}
}
public class GoodThreadDesignDemo {
public static void main(String[] args) {
List<GoodThreadDesign> threads
= new ArrayList<GoodThreadDesign>();
for (int i=0; i < 10; i++) {
GoodThreadDesign gtd = new GoodThreadDesign();
gtd.setName("B" + i);
gtd.start();
}
Thread.sleep(30 * 1000); //-- sleep for thirty seconds
for (GoodThreadDesign thread : threads ) {
thread.stop();
thread.join(); //-- wait for this thread to die
}
System.out.println("All threads have gracefully exited!");
}
}
Serializing thread access:
Many a time we need to prevent multiple concurrent access to the same resource. Sometimes, we know that Threads need access to a resource, but the resource can only perform one thing at one time.
Example: Printing some data, Writing to a memory location
For such scenarios, we sometime need to sequential-ise the thread access. We achieve this through the synchronized method (meaning only one thread should execute that method at a given point of time)
A very good example of this use-case, is the printer. Let us say a thread calls a method that will print the document on the printer. Now we want this document to be printed completely before another thread starts to print the document.
If we do not have this restriction, then the document that is printed can end up having text from multiple threads
public void print(String textToPrint) {
//-- this method will open a connection to the printer
//-- and print the string
}
If Thread 1 – calls this method with the String “The quick brown fox jumps over the lazy dog” and Thread 2 calls the same method with the String – “All is well that ends Well” at the same time, it is possible that the output on the printer can become jumbled with both the strings.
We would want Thread 1 to complete the printing before Thread 2 starts to print. This can be achieved by synchronizing the method using the synchronized keyword.
public void synchronized print(String textToPrint) {
//-- this method will open a connection to the printer
//-- and print the string
//-- this method is synchronized - so no need to worry
//-- about the output getting jumbled with multiple threads
}
So, keep in mind that whenever you want to serialize access to threads, and want only one thread to execute a given method at a time, then be sure to add the synchronized keyword to the method signature.
Communicating between threads:
The preferred way, if not the only way, for threads to communicate with one another is to use the wait()and notify() methods.
When a thread executes a method on an object and calls the wait() method, then that the thread is paused and placed in a WAIT state – It is resumed only when some other thread notifies the waiting thread calling the notify() method on the same object where the wait() was invoked.
Please note that the wait() and notify() methods are part of the java.lang.Object and are not part of the java.lang.Thread class.
Another important note:
The wait() and notify() methods can only be called inside on a synchronized block – if you attempt to call the wait() and notify() method without the enclosing synchronized definition, you would get a compile time error.
Let us look at at an example to understand the following: Lets learn with the help of an example:
Let us define a Queue that will hold data to be used by multiple threads.
A source, (say for example) a web server receives a lot of HTTP Requests. All the requests have to be processed immediately (at the same time) without any delay. We could need to have a fixed number of threads, servicing the impatient clients. Our queue will buffer the requests until the threads become available to pick up the request to perform service.
Here is the pseudo-code for the Queue class:
Class Q {
One data structure to hold data (probably a list)?
One Public method to add the data to the queue
One Public method to get the data from queue.
}
Here is the implementation for our Queue class:
import java.util.List;
import java.util.LinkedList;
public class WorkQueue {
private static LinkedList queue;
static {
queue = new LinkedList();
}
public void addWork(Object obj) {
queue.addLast(obj);
}
public Object getWork() throws InterruptedException {
return queue.removeFirst();
}
}
If you would observe, we used a LinkedList as the List implementation – you are free to choose any List implementation like ArrayList. Now the question is why have we made the LinkedList variable – a static variable?
Well, we want to make sure there is only one queue. Even if multiple WorkQueue classes are instantiated (accidentally) still we would be having only one “queue”. This is a fail-safe mechanism which is good to have!
Now, it is possible that the queue will be empty at some point of time. What will happen if the queue is empty and somebody requests data from the queue? The ideal thing would be for the caller to wait, if the queue is empty. Let us modify the code to take this into account.
public class WorkQueue {
private static LinkedList queue;
static {
queue = new LinkedList();
}
public void addWork(Object obj) {
queue.addLast(obj);
}
public Object getWork() throws InterruptedException {
while (queue.isEmpty()) {
wait();
}
return queue.removeFirst();
}
}
Okay, now what happens if some body is waiting for data in the queue and data finally arrives? How to we wake up someone who is waiting on us? We do this by invoking the notify method()
public class WorkQueue {
. . .
public void addWork(Object obj) {
queue.addLast(obj);
notifyAll();
}
. . .
}
Well, what do we do if somebody adds data and somebody gets data at the very exact instant? Can our data structure handle multiple data access? How fo we serialize the data access? We add the synchronized keyword to addWork() and getWork() methods – so that only one thread may call getWork() or addWork() at a given point of time.
public class WorkQueue {
. . .
public synchronized void addWork(Object obj) {
queue.addLast(obj);
notifyAll();
}
public synchronized Object getWork() throws Exception {
while (queue.isEmpty()) {
wait();
}
return queue.removeFirst();
}
}
Now let us design a Worker class which will read data from the above Queue: This Worker would need access to the queue – so we pass a reference to the Queue in the Worker’s constructor
public class Worker extends Thread {
private Queue queue; //-- worker needs access to queue
public Worker(Queue q) {
this.queue = q;
}
. . .
}
Inside the run() method, the Worker will try to call the getWork() method on the Queue. If the Queue is empty, the Worker will wait till data is available.
public class Worker extends Thread {
private Queue queue; //-- well, worker needs access to queue!
public Worker(Queue q) {
this.queue = q;
}
. . .
public void run() {
while(true) {
//-- always a busy worker
Object obj = queue.getWork();
//-- The Worker will wait till the work becomes available
//-- This is a BLOCKING call - meaning the Worker is blocked
//-- until work arrives
//-- do some work with the object
}
}
. . .
}
The Object obj = queue.getWork(); is a blocking call – meaning it will wait until data becomes available to the worker – Once the data becomes available, the worker can use the data and do some work
What we saw just now is the classic Consumer – Producer example in Java.
We have purposefully avoided using the JDK 1.5 concurrent package – so that we get to really understand what goes behind the scenes in concurrent programs. Once you get a solid understanding of the above example, feel free to explore the java.util.concurrent package.
Summary:
Threads spell concurrency. Java applications are generally high availability, robust and high performance systems. Having a solid understanding of Threads is important in the path to excellence of a Java programmer. In the chapter, we learned the following
- How to create a Thread
- Starting a Thread
- Naming a Thread
- .How to have a Thread run continuously
- How to have a continuously running thread stoppable from outside the thread
- Synchronizing access to threads
- Inter-thread communication using the wait() and notify() methods