Read/write locks, as the name suggests. This is a synchronizer tool for two different behaviors, and reading and writing are mutually exclusive behaviors (while writing, you cannot read). Cannot write while reading), so the ReentrantReadWriteLock lock also has this feature.

Understand read and write locks using the process of writing company brochures

Imagine that you are writing an internal management brief for your company and you only have one pen (write lock), so you can write only when you get the pen (write lock). Once written (release the lock), the brief can be posted for colleagues to read (read simultaneously: read the lock). But at this time you find some problems written in the brochure, need to be revised. However, you need to wait until these colleagues have finished reading (release read lock) before you can take pen (write lock) to revise the brief. Once you’ve made the change, you decide to check it for yourself (hold the write lock, then hold the read lock), put down the pen (release the write lock, this process is degraded: write lock -> read lock), and then post it on the bulletin board for your colleagues to see.

2

The source code parsing

Here as long as to learn its design, for fair lock is not fair lock, and the concept of reentrant is no longer described, not too clear can refer to AQS- with a key and safe understand ReentrantLock this article analysis. Read/write locks also inherit from the AQS synchronizer to achieve their functions. It uses both exclusive locks (write locks) and shared locks (read locks). Now let’s see how it implements read locks and write locks together.

Constructor:

  public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);  // sync = lock.sync;
        writerLock = new WriteLock(this); // sync = lock.sync;
    }
Copy the code

Read and write locks, ultimately using the Sync object constructed here, which generates fair or unfair locks based on the fair argument. Let’s look at read lock acquisition/release and write lock acquisition/release.


/ / read lock
        public void lock(a) {
            sync.acquireShared(1);
        }
        public void unlock(a) {
            sync.releaseShared(1);
        }
/ / write locks
          public void lock(a) {
            sync.acquire(1);
        }

         public void unlock(a) {
            sync.release(1);
        }
Copy the code

As you can see, read locks use shared lock logic, while write locks use exclusive lock logic, and note that they are reentrant. It’s all very simple, as we’ve covered in previous articles. The challenge here is how do read locks and write locks relate? ReentrantReadWriteLock ¶ ReentrantReadWriteLock ¶ ReentrantReadWriteLock ¶

        static final int SHARED_SHIFT   = 16;
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1; // Maximum number of read locks
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

        /** Number of shared locks lent */
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
        /** Number of write locks */
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
Copy the code

Int32 is divided into two parts: the first 16 bits (store read lock information) and the last 16 bits (store write lock information). We again according to its provide sharedCount, exclusiveCount method name can understand, ReentrantReadWriteLock read-write lock is access to read lock number and number of write locks to implement, so as to realize mutual exclusion function. Now let’s look at the actual code:

Read lock:


// Read lock sync.acquireshared (1) and finally call tryReleaseShared
  protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            int c = getState();
            if(exclusiveCount(c) ! =0&& # #1getExclusiveOwnerThread() ! = current)// Check whether the lock can be degraded (hold write lock, obtain read lock)
                return -1; / / failure. Because someone already holds the write lock
            int r = sharedCount(c); // The number of locks to read
            if(! readerShouldBlock() && ##2
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {   ## 3
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null|| rh.tid ! = getThreadId(current)) cachedHoldCounter = rh = readHolds.get();else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            return fullTryAcquireShared(current); // Complete version, use for loop, code similar to not repeat.
        }
Copy the code

Here’s the code:

1: If (exclusiveCount(c)! = 0 && getExclusiveOwnerThread() ! The code implements the mutual exclusion of the read/write lock and the degradation of the write lock. Note: the number of write locks is not zero, and the current thread is not the thread claiming the write lock. The read lock can be acquired only if the value is false: A No thread is currently writing the lock; B. Yes The current thread obtains the write lock.

2: The logic here is to acquire the read lock, using CAS.

FirstReader and cachedHoldCounter log the first and last threads to optimize lock acquisition efficiency. FirstReader (repeat lock acquisition if there is only one thread). CachedHoldCounter (the last thread to acquire the lock acquires the lock repeatedly). Because those are the two most likely to happen.

Write locks:

// Write lock sync. AcquireShared (1) Finally call tryReleaseShared
 protected final boolean tryAcquire(int acquires) {
                 Thread current = Thread.currentThread();
            int c = getState(); / / state values
            int w = exclusiveCount(c); // Number of write locks
            if(c ! =0) { ## 1
                // (Note: if c ! = 0 and w == 0 then shared count ! = 0)
                if (w == 0|| current ! = getExclusiveOwnerThread())return false;
                if (w + exclusiveCount(acquires) > MAX_COUNT)  // Maximum number of reentrants
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire
                setState(c + acquires);
                return true;
            }
            if (writerShouldBlock() ||        // Get read lock logic! compareAndSetState(c, c + acquires))return false;
            setExclusiveOwnerThread(current); // Set the current thread for easy reentrant
            return true;
        }
Copy the code

**1: The ** state value is not 0, which means that some thread holds the lock (write lock, read lock). if (w == 0 || current ! If (getExclusiveOwnerThread() = getExclusiveOwnerThread()), the number of write locks is 0. If the number of write locks is not zero, but not the current thread, it cannot be reentrant.

The read/write lock release is relatively simple and will not be parsed here. At the same time need to say that is about Java AQS this piece of source code learning to this first.