Java Synchronised Variable
-
the
synchronized
Keyword -
Use the
ReentrantLock
Method - Use the Binary or Counting Semaphore
- Use the Atomic Variable
This article will discuss how to synchronize or lock variables in Java.
Synchronization or locking is essential in multi-threaded programming. Threads running in parallel might try to access the same variable or other resources that produce unexpected results.
Synchronization or locking is a solution to avoid such error cases.
the synchronized
Keyword
Synchronization is a traditional method in Java. The synchronized block is implemented using the synchronized
keyword.
Once a thread enters the synchronized
code, other Java threads will be in a block state. After the current Java thread exits the synchronized
block, other Java threads can try to access the synchronized
block.
There is a drawback to this method. The synchronized
block never allows a thread to wait in a queue and access the variable after the current Java thread finishes its work, so Java threads must wait a long time.
Use the Synchronized
Keyword With a Method or Block
In the example below, we synchronize the MenuObj
in the run()
method of the MultiThreadList
class. We can also define the listItem()
code block with synchronized
.
Both functions will give the same outcome.
The same program can be as follows with the synchronization of methods.
public synchronized void listItem(String item) {
{ /*Code to Synchronize*/
}
}
synchronized (this) {
System.out.println("\nMenu Item - \t" + item);
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Listed - " + item);
}
Here, users can see the complete example code using the synchronized
block.
Example Code:
import java.io.*;
import java.util.*;
// Menu class
class Menu {
// List an item
public void listItem(String item) {
System.out.println("\nMenu Item - " + item);
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Listed - " + item);
}
}
// Multi-thread menu listing
class MultiThreadList extends Thread {
private String item;
Menu MenuObj;
/* Gets menu object and a string item*/
MultiThreadList(String m, Menu obj) {
item = m;
MenuObj = obj;
}
public void run() {
/* Only one Java thread can list an item at a time.*/
synchronized (MenuObj) {
// Menu object synchronized
MenuObj.listItem(item);
}
}
}
// Main
class MainSync {
public static void main(String args[]) {
Menu listItem = new Menu();
MultiThreadList M1 = new MultiThreadList("Rice", listItem);
MultiThreadList M2 = new MultiThreadList("Icecream", listItem);
// Start two threads
M1.start();
M2.start();
// Wait for thread completion
try {
M1.join();
M2.join();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Output:
Menu Item - Rice
Listed - Rice
Menu Item - Icecream
Listed - Icecream
In the above output, users can observe that when one thread access the MenuObj
, the other threads cannot as it prints the listed item followed by the menu item. If multiple threads try to access the same block simultaneously, it should print the menu item and the listed item in a different order.
Use the ReentrantLock
Method
The ReentrantLock
class in Java is a flexible method to lock variables. The ReentrantLock
class provides synchronization when accessing common resources or variables.
The lock()
and unlock()
methods carry out the process. One Java thread gets the lock at a time. Other threads will be in a block state during this time.
Threads can enter many times to the lock. Hence, the name is Re-entrant. When the Java thread gets the lock, the hold count is one, and the count adds up on re-entering.
The lock hold count decreases by one after unlock()
runs. The ReentrantLock
serves the threads based on the waiting time.
The Java thread with a longer waiting time gets priority.
To release the lock in case of some exceptions, the lock()
runs before the try
block. The unblock()
runs within the finally
block.
the ReentrantLock
Functions
lock() |
Increments hold count by one. If the variable is free, assign a lock to the Java thread. |
unlock() |
Decrements hold count by one. Lock releases when the hold count is zero. |
tryLock() |
If the resource is free, this function returns true. Else, the thread exits. |
lockInterruptibly() |
When one Java thread uses the lock, other Java threads can interrupt this Java thread. So, the current Java thread will have to return immediately. |
getHoldCount() |
Returns the number of locks on a resource. |
isHeldByCurrentThread() |
When the current Java thread uses the lock, the method returns true. |
Use the ReentrantLock
In this program, we create an object for ReentrantLock
. The runnable class business
executes and passes the lock to the ReentrantLock
.
Two pools are available to observe the outcome. The program uses lock()
to get the lock and unlock()
to finally release the lock.
The Boolean finished
and available
keep track of the lock availability and task completion.
Two threads are running. The second shop doesn’t get the lock and waits in the queue.
Shop 1 gets the outside lock first and the inside lock next. Shop 2 is waiting at this time.
Shop 1’s lock hold count is two. Shop 1 releases the inside lock and then the outside, and then the lock hold count decreases, and Shop 1 closes.
Shop 2 gets the lock automatically through the queue. The process repeats for Shop 2.
Example Code:
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
class business implements Runnable {
String name;
ReentrantLock re;
public business(ReentrantLock rl, String n) {
re = rl;
name = n;
}
public void run() {
boolean finished = false;
while (!finished) {
// Get Outer Lock
boolean isLockAvailable = re.tryLock();
// If the lock is available
if (isLockAvailable) {
try {
Date d = new Date();
SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
System.out.println("Shop - " + name + " got outside lock at " + ft.format(d));
Thread.sleep(1500);
// Get Inner Lock
re.lock();
try {
d = new Date();
ft = new SimpleDateFormat("hh:mm:ss");
System.out.println("Shop - " + name + " got inside lock at " + ft.format(d));
System.out.println("Lock Hold Count - " + re.getHoldCount());
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// Inner lock release
System.out.println("Shop - " + name + " releasing inside lock");
re.unlock();
}
System.out.println("Lock Hold Count - " + re.getHoldCount());
System.out.println("Shop - " + name + " closed");
finished = true;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// Outer lock release
System.out.println("Shop - " + name + " releasing outside lock");
re.unlock();
System.out.println("Lock Hold Count - " + re.getHoldCount());
}
} else {
System.out.println("Shop - " + name + " waiting for lock");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class sample {
static final int POOL_MAX = 2;
public static void main(String[] args) {
ReentrantLock rel = new ReentrantLock();
ExecutorService pool = Executors.newFixedThreadPool(POOL_MAX);
Runnable p1 = new business(rel, "Shop 1");
Runnable p2 = new business(rel, "Shop 2");
System.out.println("Running Pool 1");
pool.execute(p1);
System.out.println("Running Pool 2");
pool.execute(p2);
pool.shutdown();
}
}
Output:
Running Pool 1
Running Pool 2
Shop - Shop 2 waiting for the lock
Shop - Shop 1 got the outside lock at 11:05:47
Shop - Shop 2 waiting for the lock
Shop - Shop 1 got the inside 'lock' at 11:05:48
Lock Hold Count - 2
Shop - Shop 2 waiting for the lock
Shop - Shop 2 waiting for the lock
Shop - Shop 1 releasing the inside lock
Lock Hold Count - 1
Shop - Shop 1 closed
Shop - Shop 1 releasing the outside lock
Lock Hold Count - 0
Shop - Shop 2 got the outside lock at 11:05:51
Shop - Shop 2 got the inside 'lock' at 11:05:52
Lock Hold Count - 2
Shop - Shop 2 releasing the inside lock
Lock Hold Count - 1
Shop - Shop 2 closed
Shop - Shop 2 releasing the outside lock
Lock Hold Count - 0
Use the Binary or Counting Semaphore
Semaphore in Java decides the number of threads accessing a resource together. Binary Semaphore gives a pass or a permit to access a resource in multi-threaded programming.
Java threads need to wait until a lock permit is available. The acquire()
method gives the pass, and the release()
function releases the lock pass.
The acquire()
function blocks the threads until the lock permit becomes available.
The two states available in a Semaphore
are the permit available
and permit not available
.
In this example, a Semaphore
object accesses the commonResource()
function. Two threads are running in the program.
The acquire()
method gives a permit to the threads, and release()
releases the lock permit based on availability.
Thread 0 gets the pass and enters the busy space. Then, it comes out to the free space upon releasing the permit.
Next, thread 1 obtains the lock permit, enters the critical region, and comes to the free space after the permit release.
Example Code:
import java.util.concurrent.Semaphore;
public class SemaphoreCounter {
Semaphore binary = new Semaphore(1);
public static void main(String args[]) {
final SemaphoreCounter semObj = new SemaphoreCounter();
new Thread() {
@Override
public void run() {
semObj.commonResource();
}
}.start();
new Thread() {
@Override
public void run() {
semObj.commonResource();
}
}.start();
}
private void commonResource() {
try {
binary.acquire();
// mutual sharing resource
System.out.println(Thread.currentThread().getName() + " busy space");
} catch (InterruptedException ie) {
ie.printStackTrace();
} finally {
binary.release();
System.out.println(Thread.currentThread().getName() + " free space");
}
}
}
Output:
Thread-0 busy space
Thread-0 free space
Thread-1 busy space
Thread-1 free space
Use the Atomic Variable
Atomic variables provide synchronization in Java multi-threaded programming with its built-in functions.
The program sets a timer limit and thread size. The executorService
submits the timer by looping to the thread limit and shuts down the service.
The timer
object of the Atomic variable increases the timer. The entire process happens in a synchronized way.
Each Java thread gets a unique count without interrupting.
Example Code:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
class Timer implements Runnable {
private static AtomicInteger timer;
private static final int timerLimit = 10;
private static final int totThreads = 5;
public static void main(String[] args) {
timer = new AtomicInteger(0);
ExecutorService executorService = Executors.newFixedThreadPool(totThreads);
for (int i = 0; i < totThreads; i++) {
executorService.submit(new Timer());
}
executorService.shutdown();
}
@Override
public void run() {
while (timer.get() < timerLimit) {
increaseTimer();
}
}
private void increaseTimer() {
System.out.println(Thread.currentThread().getName() + " : " + timer.getAndIncrement());
}
}
Output:
pool-1-thread-2 : 4
pool-1-thread-2 : 5
pool-1-thread-4 : 1
pool-1-thread-4 : 7
pool-1-thread-4 : 8
pool-1-thread-4 : 9
pool-1-thread-3 : 3
pool-1-thread-5 : 2
pool-1-thread-1 : 0
pool-1-thread-2 : 6
This article taught us four methods to synchronize a resource in Java in multi-threaded programming. The synchronized
method is the traditional method.
We go for the ReentrantLock
method to avail flexibility. The ReentrantLock
also has a drawback: the programmer needs to keep track of the try-catch-finally
block when writing lock()
and unlock()
.
Even though Binary Semaphore is similar to the ReentrantLock
, the ReentrantLock
has only the basic level synchronization and fixed locking mechanism. Binary Semaphore provides high-level synchronization, custom locking, and deadlock prevention.
Atomic variables are also a method to achieve synchronization for simple data read-write activities.