Threading and Synchronization in Java
In this tutorial, we will study the concept of threads in Java. From what they are, how to classify them, break them and use them will be the main focus of this guide. Here is what we will be looking at overall in this tutorial :
- What is a Thread.
- Difference between a process, a task and a thread.
- Lifecycle of a thread.
- Multithreading in Java.
- States a thread can hold.
- Thread priorities.
- Thread class and Runnable interface.
- Synchronised block.
Multithreading was designed into Java from the very beginning. You'll see support for multithreading in special keywords and in a number of classes in the Java Standard API. Understanding threading is a very important part of being an accomplished Java developer.
Let’s dive into threads and how they handle so much!
What is a Thread?
Any program in execution is called a process. A thread is termed as a lightweight process.
>>> A process can consist of multiple threads.
A thread has its own:
- Program counter which keeps track of which instructions to execute next.
- System registers which hold its current working variables.
- A stack which contains the execution history.
Threads can share data with other threads like open files, state and code segment.
Properties of Threads
Let’s discuss some thread properties and characteristics:
- A thread is also called a lightweight process.
- Each thread belongs to exactly one process.
- No thread can exist outside a process.
- In Java, every thread in the JVM is represented by a Java object of class Thread.
- A thread in Java can have some states like New, Runnable, Blocked, Waiting, Terminated.
Process vs Threads
Process |
Thread |
Process is heavy weight. |
Thread is light weight, taking lesser resources than a process. |
If one process is blocked, then no other process can execute until the first process is unblocked. |
While one thread is blocked and waiting, a second thread in the same task can run. |
Process switching needs interaction with operating system. |
Thread switching does not need interaction with OS. |
Multiple processes without using threads use more resources. |
Multiple threaded processes use fewer resources. |
In multiple processes each process operates independently of the others. |
One thread can read, write or change another thread's data. |
The above table shows enough and basic differences between a process and threads.
Threads in Java
Even if you don't create any thread explicitly in your program, a thread called main thread is still created. Although the main thread is automatically created, you can control it by obtaining a reference to it by calling currentThread() method.
Two important things to know about main thread are,
- It is the thread from which other threads will be produced.
- main thread must always be the last thread to finish execution.
class MainThread {
public static void main(String[] args) {
Thread t = Thread.currentThread();
t.setName("MyMainThread");
System.out.print("DiscoverSDK, Thread name is " + t);
}
}
Output of the above program is very much easy to interpret:
DiscoverSDK, Thread name is [MainThread,5,main]
To create another thread in Java, there are two ways:
- Extend Thread class.
- Implements Runnable interface.
Let’s discuss these methods in their own section and discuss what way is recommended.
extends Thread class
Thread class is the main class on which Java's Multithreading system is based upon.
Thread class have some constructors which have different functionalities. Four constructors are:
- Thread ( )
- Thread ( String str ) - This method creates
- Thread ( Runnable r )
- Thread ( Runnable r, String str)
Thread class also defines many methods for managing threads. Some of them are:
Method |
Description |
setName() |
to give thread a name |
getName() |
return thread's name |
getPriority() |
return thread's priority |
isAlive() |
checks if thread is still running or not |
join() |
Wait for a thread to end |
run() |
Entry point for a thread |
sleep() |
suspend thread for a specified time |
start() |
start a thread by calling run() method |
>>> When we extend Thread class, setName() and getName() methods cannot be overridden, because they are declared final in Thread superclass.
>>> While using sleep() method, always handle the exception it throws.
Let’s dive into some code on how to create a Thread in Java. Follow the below code fragment:
class MyThread extends Thread {
public void run() {
System.out.println("Concurrent thread started running..");
}
}
classMyThreadDemo {
public static void main( String args[] ){
MyThread mt = new MyThread();
mt.start();
}
}
The extending class must override run() method which is the entry point of new thread. Output will be:
Concurrent thread started running..
Use the start() method to start and run the thread.
implements Runnable interface
The easiest way to create a thread is to create a class that implements the runnable interface. After implementing runnable interface , the class needs to implement the run() method, which is of the form,
public void run()
- run() method introduces a concurrent thread into your program. This thread will end when run() returns.
- You must specify the code for your thread inside run() method.
- run() method can call other methods, can use other classes and declare variables just like any other normal method.
Let’s look at the code snippet described above:
class MyThread implements Runnable {
public void run() {
System.out.println("concurrent thread started running..");
}
}
class MyThreadDemo {
public static void main( String args[] ) {
MyThread mt = new MyThread();
Thread t = new Thread(mt);
t.start();
}
}
Output for the above code will be:
concurrent thread started running..
To call the run() method, start() method is used. On calling start(), a new stack is provided to the thread and run() method is called to introduce the new thread into the program.
Calling run() without start()
In the above program if we directly call run() method, without using start() method,
public static void main( String args[] ) {
MyThread mt = new MyThread();
mt.run();
}
Doing so, the thread won't be allocated a new call stack, and it will start running in the current call stack, that is the call stack of the main thread. Hence Multithreading won't be there.
implement Runnable vs extends Thread
Runnable is the preferred way to do it. We’re not really specialising the thread's behaviour and just giving it something to run. That means composition is the philosophically "purer" way to go.
In practical terms, it means we can implement Runnable and extend from another class as well, giving free signal to use more super properties.
Forcibly stopping a Thread
We can stop a thread using stop() method on it though it is not recommended at all. This is so important that we will state it again.
>>> Do not call the deprecated stop() method. It is unsafe. It causes the thread to abort no matter what it's doing, and it could be in the middle of executing a critical section with data in an inconsistent state.
The synchronized Statement
Every object has a monitor which can be locked and unlocked. The monitor can only be owned by one thread at a time. If the monitor is owned by t1 and a different thread t2 wants it, t2 blocks. When the monitor gets unlocked, the threads blocked on the monitor compete for it and only one of them gets it.
The monitor for object o is only acquired by executing a synchronized statement on o:
synchronized (o) {
...
}
The lock is freed at the end of the statement (whether it completes normally or via an exception).
You can mark methods synchronized as shown in following snippet:
class C {
synchronized void p() {...}
static synchronized void q() {...}
...
}
but this is just syntactic sugar for
class C {
void p() {synchronized (this) {...}}
static void q() {synchronized(C.class) {...}}
...
}
The synchronized statement is used to enforce mutual exclusion. When multiple threads access the same piece of data, it's imperative that one thread not see the data in an intermediate state. Example is shown:
class PriorityQueue {
// Implemented as a binary heap
private Object[] data;
private int size;
...
public synchronized add(Object o) {
if (size == data.length) {
throw new HeapFullException();
}
data[size++] = o;
siftUp();
}
public synchronized Object remove() {
...
}
public synchronized String toString() {
...
}
}
If the methods add() and remove() are not synchronized several problems could occur.
Advantages of Threads
- Threads minimize the context switching time.
- Use of threads provides concurrency within a process.
- Efficient communication.
- It is more economical to create and context switch threads.
- Threads allow utilization of multiprocessor architectures to a greater scale and efficiency.
Conclusion
In this tutorial, we studied the concept of threads in Java. From what they are, how to classify them, break them and used them. We also had a look at synchronisation.
Recent Stories
Top DiscoverSDK Experts
Compare Products
Select up to three two products to compare by clicking on the compare icon () of each product.
{{compareToolModel.Error}}
{{CommentsModel.TotalCount}} Comments
Your Comment