Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Lock and Condition | High-level Synchronization Mechanisms
Multithreading in Java
course content

Contenido del Curso

Multithreading in Java

Multithreading in Java

1. Multithreading Basics
2. Synchronized Collections
3. High-level Synchronization Mechanisms
4. Multithreading Best Practices

bookLock and Condition

In Java, standard synchronization is based on the synchronized keyword and built-in monitor objects. However, in some cases, synchronized may not be sufficient, especially when greater flexibility in thread management is required.

General Description

The Lock interface and the Condition interface, introduced in the java.util.concurrent.locks package, provide advanced thread management capabilities.

In this picture, you can see that the first thread captures the lock using the lock() method, and at this moment, another thread cannot capture the same lock. As soon as all the code inside the lock is executed, it will call the unlock() method and release the lock. Only after that can the second thread capture the lock.

Difference

The difference between these two interfaces is that the Lock implementations are a high-level alternative to the synchronized block, and the Condition interface implementations are an alternative to the notify()/wait() methods. Both of these interfaces are part of the java.util.concurrent.locks package.

Real-Life Examples

Imagine you are managing a queue for registration for an event. To prevent overflow and ensure proper seat allocation, you need to use blocking mechanisms and conditions to keep the flow of new registrations waiting until a free seat becomes available.

ReentrantLock Class

The ReentrantLock class from the java.util.concurrent.locks package is an implementation of the Lock interface. It provides functionality for explicitly managing locks.

The main methods of ReentrantLock:

  • lock(): Captures a lock;
  • unlock(): Releases the lock;
  • tryLock(): Tries to capture the lock and returns true if the capture is successful;
  • tryLock(long timeout, TimeUnit unit): Tries to capture the lock for the specified time;
  • newCondition(): Creates a condition for the current Lock.
java

Main

copy
12345678910111213141516171819202122232425262728293031
package com.example; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Main { // Creating a ReentrantLock object private final Lock lock = new ReentrantLock(); private int count = 0; // Method to increment the `count` variable public void increment() { lock.lock(); // Acquiring the `lock` try { count++; System.out.println("Count incremented to: " + count); } finally { lock.unlock(); // Releasing the `lock` } } public static void main(String[] args) { Main example = new Main(); // Runnable task to call the increment method Runnable task = example::increment; // Starting multiple threads to execute the task for (int i = 0; i < 5; i++) { new Thread(task).start(); } } }

As you can see, in the increment() method we use locking with Lock. When a thread enters the method it captures the lock in lock.lock() and then executes the code and then in the finally block we release the lock in lock.unlock() so that other threads can enter.

We release the lock in the finally block for a reason, it's just that this block is almost always executed, even with exceptions, except when we terminate the program.

Condition Interface

We can only create a Condition with a binding to a specific Lock implementation. For this reason, the Condition methods will only impact the locking of that particular Lock implementation.

java

Main

copy
12
private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition();

The main methods of Condition:

  • await(): Waits for a signal from another thread;
  • signal(): Unblocks one thread waiting on a condition;
  • signalAll(): Unblocks all threads waiting on the condition.
java

Main

copy
12345678910111213141516
private final ReentrantLock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); private boolean ready = false; public void waitForCondition() throws InterruptedException { lock.lock(); // Acquire the `lock` try { while (!ready) { // Check if the condition is not met System.out.println("Waiting..."); // Print a waiting message condition.await(); // Wait for the condition to be signaled } System.out.println("Condition met!"); // Print a message when the condition is met } finally { lock.unlock(); // Release the `lock` } }

The waitForCondition() method blocks the thread until the ready variable becomes true, which signals that the condition has been met. When the condition is met, the thread continues running, displaying the message “Condition met!”

Note

When the await() method is called, the thread is paused and also releases the lock it has captured. When the thread wakes up, it should capture the lock again and only then it will start executing!

Code Example

Now let's look at an example of using ReentrantLock and Condition to manage registration for an event:

A Short Clip from the Video

Locking with ReentrantLock: The register() method captures the lock with lock.lock() to prevent multiple threads from executing code at the same time.

Condition with Condition: If there are no available spaces, the thread calls spaceAvailable.await() to wait until space is available.

Release Lock: When a thread has released space using the cancel() method, it calls spaceAvailable.signalAll() to notify all waiting threads.

Exception handling: Using try-finally blocks ensures that the lock is released even if an exception occurs.

Conclusion

The use of Lock and Condition in Java allows more flexible control over threads and synchronization than the traditional synchronized mechanism. This is especially useful in complex scenarios where more precise control over threads and wait conditions is required.

¿Todo estuvo claro?

¿Cómo podemos mejorarlo?

¡Gracias por tus comentarios!

Sección 3. Capítulo 1
some-alt