Thread synchronization
When multiple threads are executing the same code and are sharing the same set of variables or data, there is a chance of data inconsistency.
Example,
Consider the online ticket reservation system.
Suppose two users are viewing the available seat, both select the same seat and click on book ticket.
Now, there will be two threads on the server running the same code to book and reserve the seat.
In this case, the same seat will be booked for two different persons.
This is a simple scenario where :
if one thread is executing the ticket reservation code,
other thread should only be allowed to execute it after first thread completes.
This is called Thread synchronization and synchronized
keyword is one way of achieving it in java.
This article will explain the use of synchronized
keyword in java and how to achieve thread synchronization to achieve it.
It will also explain synchronized blocks, methods, what objects to use with example programs.
synchronized keyword
synchronized
keyword is used to achieve thread synchronization in java so that only one thread executes a given code at a time.
The code that should be executed by a single thread needs to be decided by the programmer as per the application design.synchronized
can be applied at following locations
1. In the method signature
Example,
public synchronized bookTicket() { // only one thread should enter here at a time }
2. Around a set of statements
Example,
public boolean bookTicket() { // concurrent multiple threads here synchronized(object) { // only one thread here at a time } // concurrent multiple threads here }
Using synchronized
affects performance. As such, only the code that requires thread-safety should be enclosed between synchronized block.
Thus, rather than placing synchronized
over entire method, a synchronized block should be preferred.
Using synchronized requires an object, also called a lock(discussed in detail later).
3. In a static method signature
Example,
public static synchronized checkBalance() { // only one thread at a time }
4. Around a set of statements in static method
Example,
public static void withdraw(Long accountId) { // multiple threads synchronized(object) { // only one thread } // multiple threads }
Object Lock
Thread synchronization is achieved using object locks. Each object in java has one and only one lock associated with it.
A thread should own this lock before entering synchronized method or block. How will a thread get an object’s lock?
There is no special step for a thread to get a lock.
When a thread tries to enter a synchronized block or method of an object and the lock of object is not taken by any other thread, it automatically enters the synchronized block/method.
Any other thread that wants to execute this block, will have to wait since the lock of object is not available.
As soon as the first thread completes the synchronized block, the lock is released and the waiting thread enters synchronized block. Refer image below
If more than one thread are waiting for the lock on the same object or to execute the synchronized block, only one of them gets the lock.
Which thread will get it is chosen by the thread scheduler and it is random.
Remember that the threads will wait only when the object on which they have to execute synchronized block is the same. If the objects are different, the thread does not need to wait.
Synchronization Example
Below is an example which shows the need for thread synchronization and how to do it.
Suppose there is a class which implements Runnable
interface and contains run()
method.
class Task implements Runnable { Booking booking; String customerId; public Task(Booking obj, String customerId) { this.booking = obj; this.customerId = customerId; } @Override public void run() { booking.bookTicket(customerId); } }
This class receives an object of Booking
class which is responsible for booking a ticket. Also, it gets a customer id for whom the ticket should be booked.
public class Booking { public void bookTicket(String customerId) { System.out.println("Booking ticket for customer " + customerId); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Ticket booked for customer " + customerId); } }
Thread.sleep()
method here represents a time taking task such as accepting payment, confirming ticket etc.
Below is a main class which creates an object of Booking class, creates two different threads and starts them.
public class Main { public static void main(String[] args) { Booking p = new Booking(); Task task1 = new Task(p, "1"); Task task2 = new Task(p, "2"); // create threads Thread t1 = new Thread(task1); Thread t2 = new Thread(task2); // start threads t1.start(); t2.start(); } }
Note that the object of Booking class is common for both the threads.
Imagine it as the real time booking scenario where two customers are booking a ticket. When this program is executed, below is the output
Booking ticket for customer 1
Booking ticket for customer 2
Ticket booked for customer 1
Ticket booked for customer 2
Tickets for both the customers are being booked simultaneously. What if both the customers have selected the same seat.
This problem can be resolved if the method that books the ticket is executed by only one thread at a time. When this thread exits, other thread enters.
As stated above, this can be achieved using synchronized
keyword. Thus, Booking
class modified to use thread synchronization is given below.
public class Booking { public synchronized void bookTicket(String customerId) { System.out.println("Booking ticket for customer " + customerId); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Ticket booked for customer " + customerId); } }
Instance method which needs to be executed by a single thread is synchronized.
Now the output is
Booking ticket for customer 1
Ticket booked for customer 1
Booking ticket for customer 2
Ticket booked for customer 2
Remember that the threads execute in order only because they share the same Booking
object. If there are different Booking objects, then their order will be the same as if there is no synchronized
keyword.
Synchronized Block
In the above example, bookTicket()
method might also perform different tasks such as generating transaction id, receipt, sending confirmation email etc., before it completes.
These tasks need not be executed by a single thread, they may be executed concurrently. Thus, it makes no sense to keep other threads waiting till the executing thread exits.
So, only the code that requires single thread execution should be synchronized and this is done using synchronized block.
A synchronized block consists of a set of statements that should be executed by a single thread exclusively surrounded by synchronized
keyword followed by the object on which the thread should lock.
The statements are enclosed between curly braces. Syntax is shown earlier in this artcle.
The object in synchronized block on which the thread locks may be any object that is unique so that no other thread has it.
Mostly it is the current executing object which is also represented by this keyword.
So Booking class method may be modified as
public void bookTicket(String customerId) { synchronized(this) { System.out.println("Booking ticket for customer " + customerId); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } // this code may be executed by multiple threads System.out.println("Ticket booked for customer " + customerId); }
Here, this
refers to the current Booking
object on which bookTicket()
was invoked. Since this object is the same for both threads, only one thread could enter synchronized block.
Code written inside synchronized block has block level scope. Thus, any variables declared inside this block are not visible outside it.
static synchronized method
A static method may also be synchronized by placing synchronized
keyword in its signature. Now the question arises, what will be the lock in this case, since there is no object in static methods.
As each object has a lock , a class also has a lock associated with it.
So, static synchronized methods can be executed by the thread that owns the class lock or the one that enters the method first. Example,
public class Booking { public static synchronized void bookTicket(String customerId) { System.out.println("Booking ticket for customer " + customerId); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Ticket booked for customer " + customerId); } }
Notice that synchronized method is now static
.
Even if each thread has a separate object of Booking
class, it would not be able to enter bookTicket()
if any other thread is executing, since now the lock is at the class level.
Now, since bookTicket()
is static
, we do not need object of Booking
to invoke it. So, Task
class can be modified as
class Task implements Runnable { String customerId; public Task(String customerId) { this.customerId = customerId; } @Override public void run() { // invoke static method Booking.bookTicket(customerId); } }
and Main
class will become
public class Main { public static void main(String[] args) { Task task1 = new Task("1"); Task task2 = new Task("2"); // create threads Thread t1 = new Thread(task1); Thread t2 = new Thread(task2); // start threads t1.start(); t2.start(); } }
Note that there is no Booking
object.
static synchronized block
We learnt that in order to synchronize a block, we need an object. But, static blocks do not have any object, so how is synchronized block in a static method possible.
As stated earlier, each class also has a lock associated with it and synchronized blocks can lock on this object in static context as shown below.
class Booking { public static void bookTicket(String customerId) { synchronized(Booking.class) { System.out.println("Booking ticket for customer " + customerId); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Ticket booked for customer " + customerId); } }
Booking.class
returns an object of type java.lang.Class
which represents a java class.
Object in synchronized context
synchronized
only works fine when it is done on correct object. But, how to choose that object.
It is not necessary to use this
for synchronized instance methods or .class
for static synchronized method or block.
The object should be such that it is common among all the threads. You may even create a new object and use it as the lock such as
public class Booking { Object lock = new Object() public void bookTicket(String customerId) { synchronized(lock) { System.out.println("Booking ticket for customer " + customerId); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Ticket booked for customer " + customerId); }
Since Booking
object is common, lock
would also be common among the threads.
But you should not lock on String objects such as
synchronized("lock"){ }
This is because if the same string exists elsewhere, the compiler may use the different references for the same string and your lock object becomes different, which might lead to unexpected results and even deadlock.
Here is how you can produce a deadlock to understand it.
Reentrant lock
If a thread holds the lock of an object, it can enter synchronized method or block for that object.
Once the thread acquires the lock, it can enter another synchronized method or block which requires the same object’s lock.
This is called reentrant lock or lock reentrancy. Example,
public synchronized method() { // same lock is required synchronized(this) { // code synchronized(this) { // code } } }
A thread executing a synchronized method or block may also call another synchronized method in the same class, and since the lock will be the same, it is permissible. Example,
public synchronized method1() { // code method2(); } private synchronized method2() { // code }
Points to remember
1. Multiple threads can invoke non-synchronized methods at the same time.
2. synchronized
can be applied only to methods or blocks, not to variables or classes.
3. If a thread goes to sleep while executing a synchronized block, then it holds the threads, which means that other threads will keep waiting for the lock.
4. Two threads can execute a non-static synchronized method at the same time if they have different objects of that class but they can not execute a static synchronized method at the same time since there is only one lock per class.
5. As soon as the thread completes the execution of synchronized method or block, the lock is released.
6. If multiple threads are waiting on the same object’s lock and the thread holding the lock releases it, then which thread will be given the lock can not be guaranteed, depends on thread scheduler.
Hope the article was informative and useful.