Location>code7788 >text

Interviewer: tell me about the implementation of read/write locks?

Popularity:317 ℃/2024-08-12 17:35:17

In the actual project development, concurrent programming will certainly be used (to improve the efficiency of program execution), and the use of concurrent programming then the lock mechanism will certainly be used, because the lock is to ensure that the main means of concurrent programming.

The following locks are commonly used in Java:

  1. synchronized (built-in lock): Java language built-in keywords, JVM hierarchical locking implementation, the use of a relatively simple and intuitive.
  2. ReentrantLock: Requires explicitly acquiring and releasing locks, providing more flexible lock manipulation.
  3. ReentrantReadWriteLock: Better performance, divided into read locks and write locks, allowing multiple read threads to acquire read locks at the same time, while write locks are exclusive.
  4. StampedLock: The locks provided by JDK 8 provide an optimistic read that attempts to read first, and if no write operation occurs during the read, the read can be completed directly, avoiding the overhead of acquiring a read lock.

Instead, we're going to focus on the read/write lock ReentrantReadWriteLock and how it's implemented.

1. Introduction to read/write locks

ReentrantReadWriteLock is a class in the Java Concurrency Package () that implements a reentrant read/write lock.Read and write locks allow multiple threads to read a shared resource at the same time, but only one thread is allowed to write to a shared resource when the

It divides locks into two parts: read locks and write locks, where read locks allow more than one thread to acquire them at the same time because the read operation itself is thread-safe, while write locks are mutually exclusive and do not allow more than one thread to acquire a write lock at the same time, and the write operation and the read operation are also mutually exclusive.

That means read and write locks are characterized:

  1. The read-read operation is unlocked.
  2. Read-write operations are locked.
  3. The write-write operation is locked.

2. Basic use

The ReentrantReadWriteLock locks are divided into the following two types:

  1. Indicates a read lock: It provides a lock method for adding a lock and an unlock method for unlocking a lock.
  2. Indicates a write lock: It provides a lock method for adding a lock and an unlock method for unlocking a lock.

Its basic use is shown in the following code:

// Create a read/write lock
final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// Acquire the read lock
final readLock = (); // Get the read lock.
// Get the write lock
final writeLock = (); // Get read lock.
// Get the read lock
(); // Get the read lock.
try {
    // Business code...
} finally {
    (); }
}
// Write lock usage
(); }
try {
    // Business code...
} finally {
    (); }
}

2.1 Reads are not mutually exclusive

Multiple threads can acquire read locks at the same time, called read-read non-mutually exclusive, as shown in the following code:

// Creating read/write locks
final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// Creating a Read Lock
final readLock = ();
Thread t1 = new Thread(() -> {
    ();
    try {
        ("[t1]Get a read lock..");
        (3000);
    } catch (InterruptedException e) {
        ();
    } finally {
        ("[t1]Release the read lock.");
        ();
    }
});
();
Thread t2 = new Thread(() -> {
    ();
    try {
        ("[t2]Get a read lock..");
        (3000);
    } catch (InterruptedException e) {
        ();
    } finally {
        ("[t2]Release the read lock.");
        ();
    }
});
();

The results of the execution of the above program are as follows:

2.2 Read and Write Mutual Exclusion

The simultaneous use of read and write locks is mutually exclusive (that is, they cannot be acquired at the same time), which is called read-write mutex, as shown in the following code:

// Creating read/write locks
final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// Creating a Read Lock
final readLock = ();
// Creating a Write Lock
final writeLock = ();
// Using Read Locks
Thread t1 = new Thread(() -> {
    ();
    try {
        ("[t1]Get a read lock..");
        (3000);
    } catch (InterruptedException e) {
        ();
    } finally {
        ("[t1]Release the read lock.");
        ();
    }
});
();
// Using Write Locks
Thread t2 = new Thread(() -> {
    ();
    try {
        ("[t2]Getting a Write Lock.");
        (3000);
    } catch (InterruptedException e) {
        ();
    } finally {
        ("[t2]Release Write Lock.");
        ();
    }
});
();

The results of the execution of the above program are as follows:

2.3 Write-write mutex

Multiple threads using write locks at the same time are also mutually exclusive, which is called write-write mutual exclusion, as shown in the following code:

// Creating read/write locks
final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// Creating a Write Lock
final writeLock = ();
Thread t1 = new Thread(() -> {
    ();
    try {
        ("[t1]Getting a Write Lock.");
        (3000);
    } catch (InterruptedException e) {
        ();
    } finally {
        ("[t1]Release Write Lock.");
        ();
    }
});
();

Thread t2 = new Thread(() -> {
    ();
    try {
        ("[t2]Getting a Write Lock.");
        (3000);
    } catch (InterruptedException e) {
        ();
    } finally {
        ("[t2]Release Write Lock.");
        ();
    }
});
();

The results of the execution of the above program are as follows:

2.4 Strengths analysis

  1. Improved program execution performance: Multiple read locks can be executed at the same time, which improves the execution performance of the program compared to ordinary locks that have to be queued for execution in all cases.
  2. Avoid reading temporary data: Read locks and write locks are mutually exclusive and queued for execution, which ensures that read operations do not read half-written temporary data.

2.5 Applicable scenarios

Read-write locks are suitable for business scenarios with more reads and fewer writes, when the advantages of read-write locks are greatest.

3. Underlying implementation

ReentrantReadWriteLock is based on the AbstractQueuedSynchronizer (AQS) implementation, which represents its state as a single atomic variable of type int and is thread-safe through CAS operations.

This was also discovered through the ReentrantReadWriteLock source code, where the fair lock in ReentrantReadWriteLock inherits from AbstractQueuedSynchronizer (AQS):

And the non-fair lock in ReentrantReadWriteLock inherits from the fair lock (which inherits from AbstractQueuedSynchronizer):

So it can be seen that the underlying ReentrantReadWriteLock is mainly implemented through AQS.

AbstractQueuedSynchronizer (AQS) is an abstract class in the Java Concurrency Package, located in the package. It provides a framework for implementing blocking locks and related synchronizers that rely on the "exclusive" and "shared" models.

AQS is the basis for many advanced synchronization tools such as ReentrantLock, ReentrantReadWriteLock, CountDownLatch, and Semaphore.

4.1 AQS Core Concepts

There are two primary elements in AQS:

  1. Synchronization State (State): Used to represent the state of the synchronizer, such as the number of locks held, the number of resources available, and so on. It can be manipulated by getState(), setState() and compareAndSetState() methods.
  2. Waiting queue (CLH queue): A waiting thread queue implemented by a bidirectional linked table. When a thread fails to acquire synchronization status, it is encapsulated as a node and added to the wait queue.

4.2 AQS Workflow

The AQS workflow is divided into two main sections.

  1. Adding and Releasing Locks
    • The thread tries to get the synchronization status, and if it succeeds, it directly executes the subsequent operations.
    • If the fetch fails, the current thread is encapsulated as a node added to the wait queue and the current thread is blocked.
    • When the thread holding the lock releases it, it wakes up the succeeding node threads in the waiting queue, causing them to retry to acquire the lock.
  2. Waiting and Waking
    • Nodes in the wait queue wait to be woken up by spinning and blocking.
    • The wakeup operation selects the nodes in the waiting queue for wakeup according to certain rules.

Post-lesson Reflections

How does AQS implement exclusive and shared locks and what design patterns does AQS use?

This article has been included in my interview mini-site, which contains modules such as Redis, JVM, Concurrency, Concurrency, MySQL, Spring, Spring MVC, Spring Boot, Spring Cloud, MyBatis, Design Patterns, Message Queuing and more.