HowToDoInJava

  • Java 8
  • Regex
  • Concurrency
  • Best Practices
  • Spring Boot
  • JUnit5
  • Interview Questions
  • Dark Mode

How to Use Locks in Java | java.util.concurrent.locks.Lock Tutorial and Example

By Lokesh Gupta | Filed Under: Java Concurrency

We are already aware of basic concepts around thread synchronization and various mechanisms using synchronized keyword. Java provides another mechanism for the synchronization of blocks of code based on the Lock interface and classes that implement it (such as ReentrantLock). In this tutorial, we will see a basic usage of Lock interface to solve printer queue problem.

Lock Interface

A java.util.concurrent.locks.Lock is a thread synchronization mechanism just like synchronized blocks. A Lock is, however, more flexible and more sophisticated than a synchronized block. Since Lock is an interface, you need to use one of its implementations to use a Lock in your applications. ReentrantLock is one such implementation of Lock interface.

Here is the simple use of Lock interface.

Lock lock = new ReentrantLock();

lock.lock();

//critical section

lock.unlock();

First a Lock is created. Then it’s lock() method is called. Now the Lock instance is locked. Any other thread calling lock() will be blocked until the thread that locked the lock calls unlock(). Finally unlock() is called, and the Lock is now unlocked so other threads can lock it.

Difference between Lock Interface and synchronized keyword

The main differences between a Lock and a synchronized block are:

1) Having a timeout trying to get access to a synchronized block is not possible. Using Lock.tryLock(long timeout, TimeUnit timeUnit), it is possible.
2) The synchronized block must be fully contained within a single method. A Lock can have it’s calls to lock() and unlock() in separate methods.

Simulating Printer Queue using Locks

In this example, program will simulate the behavior of a printer. You can submit a number of print jobs to printer during varying time interval or simultaneously. Printer will take a job from printer queue and print it. Rest of jobs will wait there for their turn. Once printer is done with print job in hand, it will pick another job from queue and start printing. Keep this happening in a loop.

PrintingJob.java

This class represents an independent printing which could be submitted to printer. This class implements Runnable interface, so that printer can execute it when it’s turn come.

class PrintingJob implements Runnable
{
   private PrinterQueue printerQueue;

   public PrintingJob(PrinterQueue printerQueue)
   {
      this.printerQueue = printerQueue;
   }

   @Override
   public void run()
   {
      System.out.printf("%s: Going to print a document\n", Thread.currentThread().getName());
      printerQueue.printJob(new Object());
   }
}

PrinterQueue.java

This class represent the printer queue/ printer. A lock is maintained by printer to start new print job as soon as current print job is finished.

class PrinterQueue
{
   private final Lock queueLock = new ReentrantLock();

   public void printJob(Object document)
   {
      queueLock.lock();
      try
      {
         Long duration = (long) (Math.random() * 10000);
         System.out.println(Thread.currentThread().getName() + ": PrintQueue: Printing a Job during " + (duration / 1000) + " seconds :: Time - " + new Date());
         Thread.sleep(duration);
      } catch (InterruptedException e)
      {
         e.printStackTrace();
      } finally
      {
         System.out.printf("%s: The document has been printed\n", Thread.currentThread().getName());
         queueLock.unlock();
      }
   }
}

Let’s test our printer program:

public class LockExample
{
   public static void main(String[] args)
   {
      PrinterQueue printerQueue = new PrinterQueue();
      Thread thread[] = new Thread[10];
      for (int i = 0; i < 10; i++)
      {
         thread[i] = new Thread(new PrintingJob(printerQueue), "Thread " + i);
      }
      for (int i = 0; i < 10; i++)
      {
         thread[i].start();
      }
   }
}

Output:

Thread 0: Going to print a document
Thread 9: Going to print a document
Thread 8: Going to print a document
Thread 7: Going to print a document
Thread 5: Going to print a document
Thread 6: Going to print a document
Thread 4: Going to print a document
Thread 3: Going to print a document
Thread 2: Going to print a document
Thread 1: Going to print a document
Thread 0: PrintQueue: Printing a Job during 8 seconds :: Time - Tue Jan 06 15:19:02 IST 2015
Thread 0: The document has been printed
Thread 9: PrintQueue: Printing a Job during 1 seconds :: Time - Tue Jan 06 15:19:11 IST 2015
Thread 9: The document has been printed
Thread 8: PrintQueue: Printing a Job during 8 seconds :: Time - Tue Jan 06 15:19:12 IST 2015
Thread 8: The document has been printed
Thread 7: PrintQueue: Printing a Job during 9 seconds :: Time - Tue Jan 06 15:19:21 IST 2015
Thread 7: The document has been printed
Thread 5: PrintQueue: Printing a Job during 7 seconds :: Time - Tue Jan 06 15:19:31 IST 2015
Thread 5: The document has been printed
Thread 6: PrintQueue: Printing a Job during 5 seconds :: Time - Tue Jan 06 15:19:39 IST 2015
Thread 6: The document has been printed
Thread 4: PrintQueue: Printing a Job during 2 seconds :: Time - Tue Jan 06 15:19:44 IST 2015
Thread 4: The document has been printed
Thread 3: PrintQueue: Printing a Job during 2 seconds :: Time - Tue Jan 06 15:19:46 IST 2015
Thread 3: The document has been printed
Thread 2: PrintQueue: Printing a Job during 5 seconds :: Time - Tue Jan 06 15:19:49 IST 2015
Thread 2: The document has been printed
Thread 1: PrintQueue: Printing a Job during 5 seconds :: Time - Tue Jan 06 15:19:54 IST 2015
Thread 1: The document has been printed

The key to the example is in the printJob() method of the PrinterQueue class. When we want to implement a critical section using locks and guarantee that only one execution thread runs a block of code, we have to create a ReentrantLock object. At the beginning of the critical section, we have to get the control of the lock using the lock() method.

At the end of the critical section, we have to use the unlock() method to free the control of the lock and allow the other threads to run this critical section. If you don’t call the unlock() method at the end of the critical section, the other threads that are waiting for that block will be waiting forever, causing a deadlock situation. If you use try-catch blocks in your critical section, don’t forget to put the sentence containing the unlock() method inside the finally section.

Read More: How to create deadlock and solve it in java

You have to be very careful with the use of Locks to avoid deadlocks . This situation occurs when two or more threads are blocked waiting for locks that never will be unlocked. For example, a thread (A) locks a Lock (X) and a thread (B) locks a Lock (Y). If now, the thread (A) tries to lock the Lock (Y) and the thread (B) simultaneously tries to lock the Lock (X), both threads will be blocked indefinitely, because they are waiting for locks that will never be liberated. Note that the problem occurs, because both threads try to get the locks in the opposite order.

Happy Learning !!

TwitterFacebookLinkedinRedditPocket

About Lokesh Gupta

A family guy with fun loving nature. Love computers, programming and solving everyday problems. Find me on Facebook and Twitter.

5
Leave a Reply

This comment form is under antispam protection
4 Comment threads
1 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
4 Comment authors
This comment form is under antispam protection
  Subscribe  
newest oldest most voted
Notify of
Jeena

First of all thanks for the nice post . I am a beginner and I am very confused with lock().
What exactly is lock() locking. I mean a thread or an object or what??

So just to be clear about my understanding of locks.

queueLock.lock(); will lock queueLock

and this means only one Thread can run this block of code(Between try and catch) at a time.
is it correct?
This code is locking the block as there is only one instance of PrinterQueue. Right??
Please help me understand it.

Thanks

Vote Up0Vote Down  Reply
1 year ago
Himansu

Hi Lokesh,
If possible can you cover a comparitive study of ReentrantLock against ReadWriteLock() and ReentrantReadWriteLock()

Regards,
Himansu

Vote Up0Vote Down  Reply
3 years ago
tarun

Important Difference in Lock and synchronized blocks :

Lock implementations provide additional functionality over the use of synchronized methods and statements by providing a non-blocking attempt to acquire a lock (tryLock()), an attempt to acquire the lock that can be interrupted (lockInterruptibly, and an attempt to acquire the lock that can timeout (tryLock(long, TimeUnit)).

They allow more flexible structuring, may have quite different properties, and may support multiple associated Condition objects.

– from JavaDoc

Vote Up0Vote Down  Reply
4 years ago
Himansu Nayak

Hi Lokesh,

There is one more key difference is unlike synchronized mechanism which uses implict JVM CAS instruction, Lock uses explicit “object” locking (not sure implicitly it uses CAS). Other then lock and unlock api this “object” it self can also be locked using synchronized. Quoting the javadoc

“Note that Lock instances are just normal objects and can themselves be used as the target in a synchronized statement. Acquiring the monitor lock of a Lock instance has no specified relationship with invoking any of the lock() methods of that instance. It is recommended that to avoid confusion you never use Lock instances in this way, except within their own implementation.”

Vote Up0Vote Down  Reply
4 years ago
Lokesh Gupta

Yes, locks implicitly use CAS using sun.misc.Unsafe. Thanks for your valuable comment.

Vote Up0Vote Down  Reply
4 years ago

Search Tutorials

Java Concurrency Tutorial

  • Java Concurrency – Introduction
  • Concurrency Evolution
  • Thread Safety
  • Concurrency vs. Parallelism
  • Compare and Swap [CAS]
  • synchronized keyword
  • Object vs. Class Level Locking
  • Runnable vs. Thread
  • wait(), notify() and notifyAll()
  • Yield() vs. Join()
  • Sleep() vs. Wait()
  • Lock vs. Monitor
  • Callable + Future
  • UncaughtExceptionHandler
  • Throttling Task Submission
  • Executor Best Practices
  • Inter-thread Communication
  • Write and Resolve Deadlock

Java Concurrency Utilities

  • AtomicInteger
  • Lock
  • ThreadFactory
  • ThreadLocal
  • ExecutorService
  • ThreadPoolExecutor
  • FixedSizeThreadPoolExecutor
  • ScheduledThreadPoolExecutor
  • Semaphore
  • Binary Semaphore
  • BlockingQueue
  • DelayQueue
  • ConcurrentLinkedDeque
  • CountDownLatch
  • ForkJoinPool

Popular Tutorials

  • Java 8 Tutorial
  • Core Java Tutorial
  • Collections in Java
  • Java Concurrency
  • Spring Boot Tutorial
  • Spring AOP Tutorial
  • Spring MVC Tutorial
  • Spring Security Tutorial
  • Hibernate Tutorial
  • Python Tutorial
  • Jersey Tutorial
  • Maven Tutorial
  • Log4j Tutorial
  • Regex Tutorial

Meta Links

  • Advertise
  • Contact Us
  • Privacy policy
  • About Me

Recommended Reading

  • 10 Life Lessons
  • Secure Hash Algorithms
  • How Web Servers work?
  • How Java I/O Works Internally?
  • Best Way to Learn Java
  • Java Best Practices Guide
  • Microservices Tutorial
  • REST API Tutorial
  • How to Start New Blog

Copyright © 2016 · HowToDoInjava.com · All Rights Reserved. | Sitemap

wpDiscuz