Welcome to King of Concurrency, this is the 15th article in the series.

In the previous article, we introduced the Lock interface, the basis for locking in Java. In this article, we’ll introduce another important basic type of interface for locks in Java, the ReadWriteLock interface.

ReadWriteLock is certainly important when exploring concurrency in Java, but it’s not easy to understand. If you’ve searched for information before, you’ll have found that most of the articles describe it in obscure ways, with lengthy source code displays or only a few words that don’t get to the point or the context.

Therefore, in this article we will focus on understanding the ideas rather than interpreting the source code. We’ll cover the source code and the knowledge behind it in a later, more advanced series.

Understand the value of ReadWriteLock

To understand ReadWriteLock, first understand why it exists. In other words, what problem is it trying to solve? To that end, we might as well start from the following figure.

I don’t know if you can understand this picture, but there are three meanings:

  • Lots of threads competing for the same resource;
  • Some of these threads are read requests, some are write requests;
  • Read requests are significantly higher than write requests among multiple threads.

Does this scene sound familiar? Yes, it is a typical caching application scenario.

It is well known that caches exist to improve the read and write performance of applications. On the one hand, we need to intercept a large number of requests to read data through caching. On the other hand, we also need to update the cache from time to time. But overall, the number of times the cache is updated is much smaller than the number of times the cache is read.

The key issue in this process is that in order to maintain data consistency, we do not allow read requests to get dirty data while reading or writing to the cache, which requires locking. The more critical point, however, is that while reads and writes need to be mutually exclusive, reads cannot be mutually exclusive.

To sum up, this problem mainly has the following points:

  • Data can be read by multiple threads simultaneously, but only one thread can write;
  • There can be no write operations or write requests when reading data.
  • There can be no read requests while writing data.

If you’re still a little confused about this, the following diagram is recommended for you. It’s ReadWriteLock’s overview of the problem and its solution, and it’s the best illustration of ReadWriteLock.

ReadWriteLock is obscure and source boring until you understand it. However, once you understand the problem it’s trying to solve and the solutions it offers, it’s incredibly clever. It has two distinct locks, one of which is mutually exclusive and the other can be shared by multiple threads. The perfect coordination of the two locks solves the problem of concurrent read and write scenarios.

When you realize that source code is nothing more than queues and shares, it’s a way to implement ReadWriteLock, not a stumbling block to your understanding.

Implement ReadWriteLock independently

Once you understand the problem behind ReadWriteLock and how it works, you can implement a read-write lock yourself without the source code in the JDK.

public class ReadWriteLock{

  private int readers       = 0;
  private int writers       = 0;
  private int writeRequests = 0;

  public synchronized void lockRead(a) throws InterruptedException{
    while(writers > 0 || writeRequests > 0){
      wait();
    }
    readers++;
  }

  public synchronized void unlockRead(a){
    readers--;
    notifyAll();
  }

  public synchronized void lockWrite(a) throws InterruptedException{
    writeRequests++;

    while(readers > 0 || writers > 0){
      wait();
    }
    writeRequests--;
    writers++;
  }

  public synchronized void unlockWrite(a) throws InterruptedException{ writers--; notifyAll(); }}Copy the code

In the read lock lockRead(), no write requests or operations are allowed. If so, the read request will be put on hold.

In lockWrite(), read requests and other write operations are not allowed, and only one write request is allowed.

This is a simple autonomous implementation of read/write locks. Of course, it’s not perfect, just a basic example. It doesn’t take into account the basic thread reentrant problem, and it’s a lot more complicated than that, but you get the idea.

ReadWriteLock in Java

Finally, let’s look at some basic ideas for ReadWriteLock implementation in the JDK. The basic relationship between ReadWriteLock and the Lock interface and other classes we discussed in the previous article is shown below:

As you can see, the implementation of read and write locks in the JDK is in the Class ReentrantReadWriteLock. ReentrantReadWriteLock contains two internal classes, ReadLock and WriteLock, which in turn implement the Lock interface.

Upgrade and degrade read/write locks

Read and write lock upgrade and degrade is an important knowledge in ReentrantReadWriteLock and a frequently used interview question.

The change from read lock to write lock is called upgrade of lock and vice versa. The most intuitive way to understand the escalation and degradation of read/write locks is to write code validation.

In code snippet 1, the read lock is acquired, then the write lock is acquired.

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        readWriteLock.readLock().lock();
        System.out.println("Read lock has been acquired...");
        readWriteLock.writeLock().lock();
        System.out.println("Write lock has been acquired..."); }}Copy the code

The following output is displayed:

Read lock has been obtained...Copy the code

Code snippet 2, acquire write lock first, then acquire read lock:

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        readWriteLock.writeLock().lock();
        System.out.println("Write lock has been acquired...");
        readWriteLock.readLock().lock();
        System.out.println("Read lock has been acquired..."); }}Copy the code

The following output is displayed:

The write lock has been obtained... Read lock has been obtained... Process finished with exit code 0Copy the code

So the result is pretty clear. ReentrantReadWriteLock supports lock degradation but does not support lock upgrade.

Fairness in read/write locks

In the previous article, we talked about the origins and consequences of thread hunger, so good concurrency utility classes are designed with fairness in mind, as is ReentrantReadWriteLock.

ReentrantReadWriteLock provides both fair and unfair modes, and the default mode is unfair. You can see it clearly in the source code snippet below.

 public ReentrantReadWriteLock(a) {
        this(false);
 }

    /**
   /**
 * Creates a new {@code ReentrantReadWriteLock} with
 * default (nonfair) ordering properties.
 */
public ReentrantReadWriteLock(a) {
  this(false);
}

/**
 * Creates a new {@code ReentrantReadWriteLock} with
 * the given fairness policy.
 *
 * @param fair {@code true} if this lock should use a fair ordering policy
 */
public ReentrantReadWriteLock(boolean fair) {
  sync = fair ? new FairSync() : new NonfairSync();
  readerLock = new ReadLock(this);
  writerLock = new WriteLock(this);
}
Copy the code

summary

That’s all for read/write locks. In this article, we’ll start with the caching problem and then look at ReadWriteLock for an easier way to understand ReadWriteLock.

The key to understanding ReadWriteLock is not to parse the source code, but to understand the thinking behind it.

In addition, we briefly introduced some of the key points in ReentrantReadWriteLock, but did not elaborate on the AQS behind it. There is no need to worry about this, we will have a detailed analysis of the introduction later.

This is the end of the text, congratulations you on a star ✨

The test of the master

  • Try adding reentrant support for read-write threads in the sample code.

Extensive reading and references

  • Sample code Reference
  • “King concurrent class” outline and update progress overview

About the author

Follow technology 8:30am for updates. Pass on quality technical articles, record the growth stories of ordinary people, and occasionally talk about life and ideals. 8:30 am push author quality original, 20:30 PM push industry depth of good articles.

If this article has been helpful to you, please like it, follow it, and monitor it as we go from bronze to King.