Java Synchronised Variable

Shubham Vora Oct 12, 2023
  1. the synchronized Keyword
  2. Use the ReentrantLock Method
  3. Use the Binary or Counting Semaphore
  4. Use the Atomic Variable
Java Synchronised 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.

Author: Shubham Vora
Shubham Vora avatar Shubham Vora avatar

Shubham is a software developer interested in learning and writing about various technologies. He loves to help people by sharing vast knowledge about modern technologies via different platforms such as the DelftStack.com website.

LinkedIn GitHub

Related Article - Java Variable