Java Threads and Concurrent Locks with Examples

From the onset Java has provided concurrency and multi-threading support built in. Since this time there have been many problems associated with multithreaded applications because of bad usage, developer errors or poorly laid out classes that led to deadlocks, starvation, race conditions or other concurrency related bugs. Java has made some progress by deprecating many of the methods that led to these conditions, these included Thread.stop(), Thread.suspend(), Thread.resume(), and Thread.destroy(). Don’t get me wrong, I think Java’s synchronized keyword allows us to synchronize critical sections in an easy manner, but sometimes we may require more control over synchronization. Enter the concurrent package released as part of JDK 1.5.

During the early part of Java development, around 1998, Doug Lea, a professor of computer science at the State University of New York Oswego released version 1.0 of the Concurrency Utilities package which would later be included into JDK 1.5 via the JSR 166. This JSR was overseen by many other experts in the field including Joshua Bloch, Sam Midkiff, David Holmes, Joseph Bowbeer and Tim Peierls.

During this tutorial we will focus primarily on using the concurrent utilities and how these can make concurrent programming easier for us.

Java Thread Locking Mechanism

In our tutorial, “Java Synchronization and Thread Safety Tutorial with Examples” we discussed the concept of thread safety and how the Java built-in synchronization using synchronized keyword can allow multiple thread execution without leading to race conditions when modifying a critical resource. If you still feel you need a full refresher on that topic please visit the post for more information.

Let’s look at the critical resource section which increments the counter using synchronized approach.

public synchronized void increment() {
  counter++;
}

public synchronized long getCounter() {
  return counter;
}

Let’s rework this simple example using concurrent ReentrantReadWriteLock mechanism. In order to provide a better use-case for our example, let’s propose the following changes as ReentrantReadWriteLocks can improve concurrency especially when there are more reader threads than writer threads, and where implementing synchronization would lead to too much contention. In my example, I have modified the application to simulate that there are five (5) reader threads which only obtain the counter value and two (2) writer threads which are performing the update operations. In my example, I am simply incrementing a counter, as I wanted to keep it simple for folks to follow.

Concurrent Locks Examples

  • Lock – The Lock interface provide much more functionality than it currently possible using Java’s basic synchronized keyword semantics in blocks or methods.
  • Condition – Works very similar to Object monitor (wait, notify and notifyAll) methods however, using Condition allows you to bind to the lock instance and create multiple wait-sets
  • ReadWriteLock – This type of lock maintains a pair of locks associated with it. One for reading (readlock) and one for writing (writelock). This type of lock makes sense when there are many simultaneous readers and few writers or when few writes are being performed on the shared resource. As the frequency of writes increases the more chances of this type of lock becoming less suitable. However, you may need to perform some profiling tests to ensure this is the right type of lock for the job.
  • ReentrantLock – This implementation of the interface looks and functions in quite the same way as does the intrinsic lock using synchronized keyword in Java. Both of these locks are reentrant, which means that if another method or block of code has already locked the monitor it can call the lock method again without blocking. In addition, it supports a fair mode flag, when true, threads contend for entry using an approximately arrival-order policy. This means that threads are granted access based on waiting time, the threads that have waited the longest are granted access first. The default is false.
  • ReentrantReadWriteLock – This lock has the same reentrant characteristics as Reentrant lock but with a ReadWriteLock implementation.

Lock Interface Methods

MethodDescription
lock()Used to acquire the lock. If the lock is not available, the current thread will wait until the lock is released and can be acquired.
lockInterruptibly()Used to acquire the lock. If the lock is not available, the current thread will wait until the lock is released and can be acquired. This implementation is allowed to be interrupted and resume execution via the InterruptedException. (NOTE: According to Oracle, the ability to interrupt the lock acquisition in some implementation may not be possible)
newCondition()Works very similar to Object monitor (wait, notify and notifyAll) methods however, using Condition allows you to bind to the lock instance and create multiple wait-sets. This is a similar replacement of the Objects monitor methods.
tryLock()Attempts to acquire the lock if it is available at the time is it called. It will return true when the lock is acquired, otherwise it returns false.
tryLock(long time, TimeUnit unit)Attempts to acquire the lock if it is available at the time is it called but will wait for a given amount of time (using unit). It will return true when the lock is acquired, otherwise it returns false if the timeout has elapsed. (Timeunit: DAYS, HOURS, MINUTES, SECONDS, MILLISECONDS, MICROSECONDS, NANOSECONDS)
unlock()Releases the lock.

Concurrent Locking Mechanism using ReentrantLock

package com.avaldes.tutorials;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
  private final Lock lock = new ReentrantLock();
  private double AccountFunds = 0;

    public void calculateFundReturns(double amount) {
        lock.lock();

        // Always surround with try/catch block
        try {
          AccountFunds = calculateFunds(amount);
        } finally {
            lock.unlock();
        }
    }

    public double getFundReturns() {
        lock.lock();

        // Always surround with try/catch block
        try {
            return AccountFunds;
        } finally {
            lock.unlock();
        }
    }

    public double calculateFunds(double amount) {
      double funds = 0;
      
      // doSomething to calculate funds ROI 
      return funds; 
    }
}

Concurrent Locking Mechanism using ReentrantReadWriteLock

package com.avaldes.tutorials;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReentrantReadWriteLockExample {
  private static long counter = 0;
  private static volatile boolean isActive = true;
  // Use Fair Locking Mode
  private final static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);  
  private final static Lock readLock = lock.readLock();
  private final static Lock writeLock = lock.writeLock();
    
  public static long increment() {
    writeLock.lock();
    try {
      counter++;
      return counter;
    } finally {
      writeLock.unlock();
    }
  }
  
  public static long getCounter() {
    readLock.lock();
    try {
      return counter;
    } finally {
      readLock.unlock();
    }
  }
  
  public static void main(String[] args) {
    Thread reader1 = new Thread(new Reader(), "Reader_1");
    Thread reader2 = new Thread(new Reader(), "Reader_2");
    Thread reader3 = new Thread(new Reader(), "Reader_3");
    Thread reader4 = new Thread(new Reader(), "Reader_4");
    Thread reader5 = new Thread(new Reader(), "Reader_5");
    
    
    Thread writer1 = new Thread(new Writer(), "Writer_1");
    Thread writer2 = new Thread(new Writer(), "Writer_2");

    writer1.start();
    writer2.start();
    
    reader1.start();
    reader2.start();
    reader3.start();    
    reader4.start();    
    reader5.start();    

    //  Make the Main Thread sleep for 100 milliseconds
    //  then set isActive to false to stop all threads 
    try {
      Thread.sleep(100);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    isActive = false;
    
  }
  
  private static class Reader implements Runnable {
    public void run() {
      // tight loop using volatile variable as active flag for proper shutdown
      while (isActive) {
        try {
          readCounter();
        } catch (Exception e) {
          System.out.format("%s was interrupted...\n", Thread.currentThread().getName());
          e.printStackTrace();
        }
      }
    }
    
    private void readCounter() {
      long c = getCounter();
      System.out.format("%s: Current runCount is %05d...\n", Thread.currentThread().getName(), c);
      try {
        Thread.sleep(5);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
  
  private static class Writer implements Runnable {
    public void run() {
      // tight loop using volatile variable as active flag for proper shutdown
      while (isActive) {
        try {
          writeCounter();
        } catch (Exception e) {
          System.out.format("%s was interrupted...\n", Thread.currentThread().getName());
          e.printStackTrace();
        }
      }
    }
    
    private void writeCounter() {
      long c = increment();
      System.out.format("%s: Incrementing runCount %05d...\n", Thread.currentThread().getName(), c);
      try {
        Thread.sleep(15);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}

Output of ReentrantReadWriteLock

I decided to make the reader threads sleep only for 5ms and the writer threads for 15ms so that the reader activities would, on average, outweigh the writer activities 3 to 1. Looking at the output you can see how the reader threads and certainly getting access to the critical resource far more often then are the writer threads.

Writer_1: Incrementing runCount 00001...
Reader_5: Current runCount is 00002...
Reader_4: Current runCount is 00002...
Writer_2: Incrementing runCount 00002...
Reader_2: Current runCount is 00002...
Reader_1: Current runCount is 00002...
Reader_3: Current runCount is 00002...
Reader_4: Current runCount is 00002...
Reader_5: Current runCount is 00002...
Reader_1: Current runCount is 00002...
Reader_2: Current runCount is 00002...
Reader_3: Current runCount is 00002...
Reader_4: Current runCount is 00002...
Reader_5: Current runCount is 00002...
Reader_1: Current runCount is 00002...
Reader_3: Current runCount is 00002...
Reader_2: Current runCount is 00002...
Reader_4: Current runCount is 00002...
Reader_5: Current runCount is 00002...
Writer_1: Incrementing runCount 00004...
Writer_2: Incrementing runCount 00003...
Reader_1: Current runCount is 00004...
Reader_3: Current runCount is 00004...
Reader_2: Current runCount is 00004...
Reader_4: Current runCount is 00004...
Reader_5: Current runCount is 00004...
Reader_1: Current runCount is 00004...
Reader_3: Current runCount is 00004...
Reader_2: Current runCount is 00004...
Reader_5: Current runCount is 00004...
Reader_4: Current runCount is 00004...
Reader_1: Current runCount is 00004...
Reader_2: Current runCount is 00004...
Reader_3: Current runCount is 00004...
Writer_1: Incrementing runCount 00005...
Reader_4: Current runCount is 00005...
Reader_5: Current runCount is 00006...
Writer_2: Incrementing runCount 00006...
Reader_3: Current runCount is 00006...
Reader_2: Current runCount is 00006...
Reader_1: Current runCount is 00006...
Reader_5: Current runCount is 00006...
Reader_4: Current runCount is 00006...
Reader_1: Current runCount is 00006...
Reader_3: Current runCount is 00006...
Reader_2: Current runCount is 00006...
Reader_5: Current runCount is 00006...
Reader_4: Current runCount is 00006...
Reader_3: Current runCount is 00006...
Reader_1: Current runCount is 00006...
Reader_2: Current runCount is 00006...
Reader_5: Current runCount is 00006...
Writer_1: Incrementing runCount 00008...
Writer_2: Incrementing runCount 00007...
Reader_4: Current runCount is 00006...
Reader_2: Current runCount is 00008...
Reader_1: Current runCount is 00008...
Reader_3: Current runCount is 00008...
Reader_5: Current runCount is 00008...
Reader_4: Current runCount is 00008...
Reader_2: Current runCount is 00008...
Reader_1: Current runCount is 00008...
Reader_3: Current runCount is 00008...
Reader_5: Current runCount is 00008...
Reader_4: Current runCount is 00008...
Reader_2: Current runCount is 00008...
Reader_3: Current runCount is 00008...
Reader_1: Current runCount is 00008...
Reader_5: Current runCount is 00008...
Writer_1: Incrementing runCount 00009...
Writer_2: Incrementing runCount 00010...
Reader_4: Current runCount is 00008...
Reader_2: Current runCount is 00010...
Reader_3: Current runCount is 00010...
Reader_1: Current runCount is 00010...
Reader_5: Current runCount is 00010...
Reader_4: Current runCount is 00010...
Reader_1: Current runCount is 00010...
Reader_2: Current runCount is 00010...
Reader_3: Current runCount is 00010...
Reader_4: Current runCount is 00010...
Reader_5: Current runCount is 00010...
Reader_3: Current runCount is 00010...
Reader_2: Current runCount is 00010...
Reader_1: Current runCount is 00010...
Reader_4: Current runCount is 00010...
Writer_2: Incrementing runCount 00011...
Writer_1: Incrementing runCount 00012...
Reader_5: Current runCount is 00010...
Reader_2: Current runCount is 00012...
Reader_1: Current runCount is 00012...
Reader_3: Current runCount is 00012...
Reader_4: Current runCount is 00012...
Reader_5: Current runCount is 00012...
Reader_1: Current runCount is 00012...
Reader_3: Current runCount is 00012...
Reader_2: Current runCount is 00012...
Reader_4: Current runCount is 00012...
Reader_5: Current runCount is 00012...
Reader_1: Current runCount is 00012...
Reader_3: Current runCount is 00012...
Reader_2: Current runCount is 00012...
Reader_4: Current runCount is 00012...
Writer_1: Incrementing runCount 00014...
Reader_5: Current runCount is 00013...
Writer_2: Incrementing runCount 00013...
Reader_3: Current runCount is 00014...
Reader_2: Current runCount is 00014...
Reader_1: Current runCount is 00014...
Reader_4: Current runCount is 00014...
Reader_5: Current runCount is 00014...

Related Posts

  • Java Thread, Concurrency and Multithreading Tutorial
    This Java Thread tutorial will give you a basic overview on Java Threads and introduce the entire tutorial series on concurrency and multithreading. From here, you will learn about many java thread concepts like: Thread States, Thread Priority, Thread Join, and ThreadGroups. In addition, you will learn about using the volatile keyword and examples on using wait, notify and notifyAll.
  • Java Thread States - Life Cycle of Java Threads
    Get a basic understanding of the various thread states. Using the state transition diagram we show the various states for a Java thread and the events that cause the thread to jump from one state to another.
  • Creating Java Threads Example
    In this post we cover creating Java Threads using the two mechanisms provided in Java, that is, by extending the Thread class and by implementing Runnable interface for concurrent programming.
  • Java Thread Priority Example
    In this post we cover Thread priorities in Java. By default, a java thread inherits the priority (implicit) of its parent thread. Using the setPriority() method you can increase or decrease the thread priority of any java thread.
  • Java ThreadGroup Example
    Sometimes we will need to organize and group our threads into logical groupings to aid in thread management. By placing threads in a threadGroup all threads in that group can be assigned properties as a set, instead of going through the tedious task of assigning properties individually.
  • Java Thread Sleep Example
    We seem to use this method very often to temporarily suspend the current threads execution for a specific period of time. Let's spend some time and familiarize ourselves with what this method actually does.
  • Java Thread Join Example
    In Java, using Thread.join() causes the current thread to wait until the specified thread dies. Using this method allows us to impose an order such that we can make one thread wait until the other completes doing what it needed to do, such as completing a calculation.
  • Examining Volatile Keyword with Java Threads
    When we declare a field as volatile, the JVM will guarantee visibility, atomicity and ordering of the variable. Without it the data may be cached locally in CPU cache and as a result changes to the variable by another thread may not be seen by all other threads resulting in inconsistent behaviour.
  • Java Threads Wait, Notify and NotifyAll Example
    The purpose of using notify() and notifyAll() is to enable threads to communicate with one another via some object on which to performing the locking. A thread using the wait() method must own a lock on the object. Once wait() is called, the thread releases the lock, and waits for another thread to either call notify() or notifyAll() method.
  • Java Thread Deadlock Example and Thread Dump Analysis using VisualVM
    Deadlock is a condition where several threads are blocking forever, waiting for the other to finish but they never do. This tutorial will discuss situations that will lead to Java Thread deadlock conditions and how they can be avoided. In addition, we will discuss using Java VisualVM to pinpoint and analyze the source of the deadlock conditions.
  • Java Thread Starvation and Livelock with Examples
    Starvation occurs when a thread is continually denied access to resources and as a result it is unable to make progress. Thread liveLock is a condition that closely resembles deadlock in that several processes are blocking each other. But with livelock, a thread is unable to make any progress because every time it tries the operation always fails.
  • Java Synchronization and Thread Safety Tutorial with Examples
    One of Java's many strengths come from the fact that it supports multithreading by default as has so from the very onset. One of the mechanisms that Java uses for this is via synchronization. When we use the synchronized keyword in Java we are trying limit the number of threads that can simultaneously access and modify a shared resource. The mechanism that is used in Java's synchronization is called a monitor.
  • Creating a Thread Safe Singleton Class with Examples
    In this tutorial we cover many examples of creating thread-safe singleton classes and discuss some of the shortfalls of each and provide some recommendations on best approaches for a fast, efficient and highly concurrent solution.
  • Java Threads and Concurrent Locks with Examples
    In this tutorial we will focus primarily on using the concurrent utilities and how these can make concurrent programming easier for us.

Please Share Us on Social Media

Facebooktwitterredditpinterestlinkedinmail

Leave a Reply

Your email address will not be published. Required fields are marked *