Unit - 4
Threads and Multithreading
The Java run-time system depends on threads for many things, and all the class libraries are designed with multithreading in mind. In fact, Java uses threads to enable the entire environment to be asynchronous. This helps reduce inefficiency by preventing the waste of CPU cycles.
The value of a multithreaded environment is best understood in contrast to its counterpart. Single-threaded systems use an approach called an event loop with polling. In this model, a single thread of control runs in an infinite loop, polling a single event queue to decide what to do next. Once this polling mechanism returns with, say, a signal that a network file is ready to be read, then the event loop dispatches control to the appropriate event handler. Until this event handler returns, nothing else can happen in the program. This wastes CPU time. It can also result in one part of a program dominating the system and preventing any other events from being processed. In general, in a single-threaded environment, when a thread blocks (that is, suspends execution) because it is waiting for some resource, the entire program stops running.
The benefit of Java’s multithreading is that the main loop/polling mechanism is eliminated. One thread can pause without stopping other parts of your program. For example, the idle time created when a thread reads data from a network or waits for user input can be utilized elsewhere. Multithreading allows animation loops to sleep for a second between each frame without causing the whole system to pause. When a thread blocks in a Java program, only the single thread that is blocked pauses. All other threads continue to run.
As most readers know, over the past few years, multi-core systems have become commonplace. Of course, single-core systems are still in widespread use. It is important to understand that Java’s multithreading features work in both types of systems. In a single-core system, concurrently executing threads share the CPU, with each thread receiving a slice of CPU time. Therefore, in a single-core system, two or more threads do not actually run at the same time, but idle CPU time is utilized. However, in multi-core systems, it is possible for two or more threads to actually execute simultaneously. In many cases, this can further improve program efficiency and increase the speed of certain operations.
Threads exist in several states. Here is a general description. A thread can be running. It can be ready to run as soon as it gets CPU time. A running thread can be suspended, which temporarily halts its activity. A suspended thread can then be resumed, allowing it to pick up where it left off. A thread can be blocked when waiting for a resource. At any time, a thread can be terminated, which halts its execution immediately. Once terminated, a thread cannot be resumed.
Java - Multithreading
Java is a multi-threaded programming language which means we can develop multi-threaded program using Java. A multi-threaded program contains two or more parts that can run concurrently and each part can handle a different task at the same time making optimal use of the available resources specially when your computer has multiple CPUs.
By definition, multitasking is when multiple processes share common processing resources such as a CPU. Multi-threading extends the idea of multitasking into applications where you can subdivide specific operations within a single application into individual threads. Each of the threads can run in parallel. The OS divides processing time not only among different applications, but also among each thread within an application.
Multi-threading enables you to write in a way where multiple activities can proceed concurrently in the same program.
Advantages of multithreading
● Because threads are autonomous and many operations can be performed at the same time, it does not cause the user to be blocked.
● It saves time by allowing you to do multiple procedures at once.
● Because threads are self-contained, an exception in one thread has no impact on other threads.
A thread goes through various stages in its life cycle. For example, a thread is born, started, runs, and then dies. The following diagram shows the complete life cycle of a thread.
Fig: The complete life cycle of a thread
Following are the stages of the life cycle −
- New − A new thread begins its life cycle in the new state. It remains in this state until the program starts the thread. It is also referred to as a born thread.
- Runnable − After a newly born thread is started, the thread becomes runnable. A thread in this state is considered to be executing its task.
- Waiting − Sometimes, a thread transitions to the waiting state while the thread waits for another thread to perform a task. A thread transitions back to the runnable state only when another thread signals the waiting thread to continue executing.
- Timed Waiting − A runnable thread can enter the timed waiting state for a specified interval of time. A thread in this state transitions back to the runnable state when that time interval expires or when the event it is waiting for occurs.
- Terminated (Dead) − A runnable thread enters the terminated state when it completes its task or otherwise terminates.
A thread can be made in two ways:
- By extending Thread class
- By implementing Runnable interface.
By extending Thread class
Method for creating a thread is to use the two simple steps below to construct a new class that extends the Thread class. This technique allows you more flexibility when dealing with multiple threads established with the Thread class's various methods.
Step 1:
You'll need to override the Thread class's run( ) method. This method serves as the thread's entry point, and it's where you'll put all of your business logic. The run() method has the following simple syntax:
Public void run( )
Step 2:
You can start a Thread object by executing the start() method, which executes a call to the run() method. The start() method has the following simple syntax:
Void start( );
Example
Class ThreadDemo extends Thread {
private Thread t;
private String threadName;
ThreadDemo( String name) {
threadName = name;
System.out.println("Creating " + threadName );
}
public void run() {
System.out.println("Running " + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// Let the thread sleep for a while.
Thread.sleep(50);
}
} catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
}
Public class TestThread {
public static void main(String args[]) {
ThreadDemo T1 = new ThreadDemo( "Thread-1");
T1.start();
ThreadDemo T2 = new ThreadDemo( "Thread-2");
T2.start();
}
}
Output
Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.
By implementing Runnable interface
If you want your class to be run as a thread, you can do so by implementing the Runnable interface. You'll need to take three basic actions to get started.
Step 1:
You must first implement the run() method given by the Runnable interface. This method serves as the thread's entry point, and it's where you'll put all of your business logic. The run() method has the following simple syntax:
Public void run( )
Step 2:
In the next step, you'll create a Thread object with the constructor below.
Thread(Runnable threadObj, String threadName);
ThreadObj is an instance of a class that implements the Runnable interface, and threadName is the new thread's name.
Step 3:
You can start a Thread object by executing the start() method, which executes a call to the run( ) method. The start() method has the following simple syntax:
Void start();
Example
Class RunnableDemo implements Runnable {
private Thread t;
private String threadName;
RunnableDemo( String name) {
threadName = name;
System.out.println("Creating " + threadName );
}
public void run() {
System.out.println("Running " + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// Let the thread sleep for a while.
Thread.sleep(50);
}
} catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
}
Public class TestThread {
public static void main(String args[]) {
RunnableDemo R1 = new RunnableDemo( "Thread-1");
R1.start();
RunnableDemo R2 = new RunnableDemo( "Thread-2");
R2.start();
}
}
Output
Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.
Let’s see how to create multiple threads
Example
Class MyThread implements Runnable {
String name;
Thread t;
MyThread String thread){
name = threadname;
t = new Thread(this, name);
System.out.println("New thread: " + t);
t.start();
}
Public void run() {
Try {
for(int i = 5; i > 0; i--) {
System.out.println(name + ": " + i);
Thread.sleep(1000);
}
}catch (InterruptedException e) {
System.out.println(name + "Interrupted");
}
System.out.println(name + " exiting.");
}
}
Class MultiThread {
Public static void main(String args[]) {
new MyThread("One");
new MyThread("Two");
new NewThread("Three");
Try {
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println("Main thread Interrupted");
}
System.out.println("Main thread exiting.");
}
}
Output
New thread: Thread[One,5,main]
New thread: Thread[Two,5,main]
New thread: Thread[Three,5,main]
One: 5
Two: 5
Three: 5
One: 4
Two: 4
Three: 4
One: 3
Three: 3
Two: 3
One: 2
Three: 2
Two: 2
One: 1
Three: 1
Two: 1
One exiting.
Two exiting.
Three exiting.
Java Thread isAlive() method
The isAlive() method of thread class tests if the thread is alive. A thread is considered alive when the start() method of thread class has been called and the thread is not yet dead. This method returns true if the thread is still running and not finished.
Syntax
- Public final boolean isAlive()
Return
This method will return true if the thread is alive otherwise returns false.
Example
- Public class JavaIsAliveExp extends Thread
- {
- Public void run()
- {
- Try
- {
- Thread.sleep(300);
- System.out.println("is run() method isAlive "+Thread.currentThread().isAlive());
- }
- Catch (InterruptedException ie) {
- }
- }
- Public static void main(String[] args)
- {
- JavaIsAliveExp t1 = new JavaIsAliveExp();
- System.out.println("before starting thread isAlive: "+t1.isAlive());
- t1.start();
- System.out.println("after starting thread isAlive: "+t1.isAlive());
- }
- }
Output:
Before starting thread isAlive: false
After starting thread isAlive: true
Is run() method isAlive true
The join() method
The join() method waits for a thread to die. In other words, it causes the currently running threads to stop executing until the thread it joins with completes its task.
Syntax:
Public void join()throws InterruptedException
Public void join(long milliseconds)throws InterruptedException
Example of join() method
- Class TestJoinMethod1 extends Thread{
- Public void run(){
- For(int i=1;i<=5;i++){
- Try{
- Thread.sleep(500);
- }catch(Exception e){System.out.println(e);}
- System.out.println(i);
- }
- }
- Public static void main(String args[]){
- TestJoinMethod1 t1=new TestJoinMethod1();
- TestJoinMethod1 t2=new TestJoinMethod1();
- TestJoinMethod1 t3=new TestJoinMethod1();
- t1.start();
- Try{
- t1.join();
- }catch(Exception e){System.out.println(e);}
- t2.start();
- t3.start();
- }
- }
Output:
1
2
3
4
5
1
1
2
2
3
3
4
4
5
5
As you can see in the above example,when t1 completes its task then t2 and t3 starts executing.
Example of join(long miliseconds) method
- Class TestJoinMethod2 extends Thread{
- Public void run(){
- For(int i=1;i<=5;i++){
- Try{
- Thread.sleep(500);
- }catch(Exception e){System.out.println(e);}
- System.out.println(i);
- }
- }
- Public static void main(String args[]){
- TestJoinMethod2 t1=new TestJoinMethod2();
- TestJoinMethod2 t2=new TestJoinMethod2();
- TestJoinMethod2 t3=new TestJoinMethod2();
- t1.start();
- Try{
- t1.join(1500);
- }catch(Exception e){System.out.println(e);}
- t2.start();
- t3.start();
- }
- }
Output:
1
2
3
1
4
1
2
5
2
3
3
4
4
5
5
In the above example,when t1 is completes its task for 1500 miliseconds(3 times) then t2 and t3 starts executing.
- The sleep() method doesn't release the lock.
- It is a method of Thread class
- It is the static method
- After the specified amount of time, sleep is completed.
- If a thread do not want to perform any operation for a particular amount of time(just pausing) then we should go for sleep() method
- Whenever we are using sleep() method compulsory we should handle Interrupted Exception otherwise we will get compile time error.
When we start two or more threads in a program, it's possible that numerous threads will try to access the same resource, resulting in unexpected results due to concurrency difficulties. For example, if multiple threads try to write to the same file at the same time, the data may be corrupted because one of the threads can override data, or while one thread is opening and another is closing the same file.
As a result, it's necessary to synchronize many threads' actions and ensure that only one thread can access the resource at any given time. This is accomplished through the use of monitors. In Java, each object has its own monitor, which a thread can lock or unlock. On a display, only one thread can retain a lock at a time.
Using synchronized blocks, the Java programming language makes it relatively easy to create threads and synchronize their tasks. Within this block, you retain shared resources. The general form of the synchronized statement is as follows:
Syntax
Synchronized(objectidentifier)
{
// Access shared variables and other shared resources
}
The object identifier is a reference to an object whose lock corresponds to the monitor represented by the synchronized statement. Now we'll look at two different ways to print a counter using two distinct threads. When threads are not synchronized, they publish counter values that are out of order, but when we print counter inside a synchronized() block, it prints counters that are very much in order for both threads.
Multithreading Example with synchronization
Class PrintDemo {
public void printCount() {
try {
for(int i = 5; i > 0; i--) {
System.out.println("Counter --- " + i );
}
} catch (Exception e) {
System.out.println("Thread interrupted.");
}
}
}
Class ThreadDemo extends Thread {
private Thread t;
private String threadName;
PrintDemo PD;
ThreadDemo( String name, PrintDemo pd) {
threadName = name;
PD = pd;
}
public void run() {
synchronized(PD) {
PD.printCount();
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
}
Public class TestThread {
public static void main(String args[]) {
PrintDemo PD = new PrintDemo();
ThreadDemo T1 = new ThreadDemo( "Thread - 1 ", PD );
ThreadDemo T2 = new ThreadDemo( "Thread - 2 ", PD );
T1.start();
T2.start();
// wait for threads to end
try {
T1.join();
T2.join();
} catch ( Exception e) {
System.out.println("Interrupted");
}
}
}
Output
Starting Thread - 1
Starting Thread - 2
Counter --- 5
Counter --- 4
Counter --- 3
Counter --- 2
Counter --- 1
Thread Thread - 1 exiting.
Counter --- 5
Counter --- 4
Counter --- 3
Counter --- 2
Counter --- 1
Thread Thread - 2 exiting.
Key takeaway
Using synchronized blocks, the Java programming language makes it relatively easy to create threads and synchronize their tasks. Within this block, you retain shared resources.
Every Java thread has a priority, which aids the operating system in determining the thread scheduling order.
There is a priority for each thread. Priorities are assigned a number between one and ten. Thread scheduling, in most circumstances, schedules threads according to their priority (known as preemptive scheduling). However, it is not assured because the scheduling option used by the JVM is dependent on the JVM specification.
Thread class defines three constants:
● public static int MIN_PRIORITY
● public static int NORM_PRIORITY
● public static int MAX_PRIORITY
The range of Java thread priorities is between MIN PRIORITY (a constant of 1) and MAX PRIORITY (a constant of 2). (a constant of 10). NORM PRIORITY is the default priority for all threads (a constant of 5).
Higher-priority threads are more vital to a program and should be given priority over lower-priority threads. Thread priorities, on the other hand, cannot ensure the order in which threads execute and are very platform dependent.
Example
Class TestMultiPriority1 extends Thread{
Public void run(){
System.out.println("running thread name is:"+Thread.currentThread().getName());
System.out.println("running thread priority is:"+Thread.currentThread().getPriority());
}
Public static void main(String args[]){
TestMultiPriority1 m1=new TestMultiPriority1();
TestMultiPriority1 m2=new TestMultiPriority1();
m1.setPriority(Thread.MIN_PRIORITY);
m2.setPriority(Thread.MAX_PRIORITY);
m1.start();
m2.start();
}
}
Output
Running thread name is: Thread-0
Running thread priority is:10
Running thread name is: Thread-1
Running thread priority is:1
Key takeaway
There is a priority for each thread. Priorities are assigned a number between one and ten. Thread scheduling, in most circumstances, schedules threads according to their priority.
Inter-thread communication or Co-operation is all about allowing synchronized threads to communicate with each other.
Cooperation (Inter-thread communication) is a mechanism in which a thread is paused running in its critical section and another thread is allowed to enter (or lock) in the same critical section to be executed.
It is implemented by following methods of Object class:
- Wait()
- Notify()
- NotifyAll()
1) wait() method
The wait() method causes current thread to release the lock and wait until either another thread invokes the notify() method or the notifyAll() method for this object, or a specified amount of time has elapsed.
The current thread must own this object's monitor, so it must be called from the synchronized method only otherwise it will throw exception.
Method | Description |
Public final void wait()throws Interrupted Exception | It waits until object is notified. |
Public final void wait(long timeout)throws Interrupted Exception | It waits for the specified amount of time. |
2) notify() method
The notify() method wakes up a single thread that is waiting on this object's monitor.
If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation.
Syntax: public final void notify()
3) notifyAll() method
Wakes up all threads that are waiting on this object's monitor.
Syntax: public final void notifyAll()
Understanding the process of inter-thread communication
The point-to-point explanation of the above diagram is as follows:
- Threads enter to acquire lock.
- Lock is acquired by on thread.
- Now thread goes to waiting state if you call wait() method on the object. Otherwise, it releases the lock and exits.
- If you call notify() or notifyAll() method, thread moves to the notified state (runnable state).
- Now thread is available to acquire lock.
- After completion of the task, thread releases the lock and exits the monitor state of the object.
Why wait(), notify() and notifyAll() methods are defined in Object class not Thread class?
It is because they are related to lock and object has a lock.
Difference between wait and sleep
Wait() | Sleep() |
The wait() method releases the lock. | The sleep() method doesn't release the lock. |
It is a method of Object class | It is a method of Thread class |
It is the non-static method | It is the static method |
It should be notified by notify() or notifyAll() methods | After the specified amount of time, sleep is completed. |
References:
1. Sun Certified Java Programmer for Java 6 by Kathy Sierra.
2. The Java TM Programming Language (3rd Edition) by Arnold, Holmes, Gosling, Goteti.
3. Core Java for Beginners by Rashmi Kanta Das(III Edition) Vikas Publication.
4. Java A Beginner's Guide, Fifth Edition, Tata McGra.