ReentrantReadWriteLock read-write lock

A read-write lock is different from a ReentrantLock, which allows access by only one thread at a time. A read-write lock allows access by multiple reader threads at a time, but all read and write operations are blocked during the operation of the writer thread.

Read and write state implementation

How do I record read and write status from an int value?

In ReentrantReadWriteLock, you can perform bit-by-bit cutting for the synchronization status value. Since int occupies 32 bits, it is split into two bits. The high 16 bits indicate the read status, and the low 16 bits indicate the write status.

How do I get write status values

We assume that the synchronization status value is converted to binary as follows:

0000 0000 0000 0010 | 0000 0000 0000 0101
Copy the code

The synchronization status indicates that the read status is 2 and the write status is 5

So how do we get the state value? Let’s think about the correlation operation of the lower bit:

  • 1 if both bits and operation (&) are 1, 0 otherwise
  • Or operator (|) two Numbers have a 1 to 1, otherwise 0

| operation rule of knowing the & if we can do this, with the following binary sync status value & operations

0000 0000 0000 0000 | 1111 1111 1111 1111
Copy the code

The results can be

0000 0000 0000 0000 | 0000 0000 0000 0101
Copy the code

Which is the binary representation of the write state, with a value of 5. So

0000 0000 0000 0000 | 1111 1111 1111 1111
Copy the code

This bit is decimal with the operand, which is 65535 (2^15 + 2^14 +….. Plus 2 to the 0, which is equal to 2 to the 16 minus 1, which is equal to (1 << 16) minus 1. This is also a constant implementation defined internally by ReentrantReadWriteLock:

static final int SHARED_SHIFT   = 16;
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
Copy the code

How do I get read status values

Getting the read state is easier than getting the write state, just synchronize the status value >> 16 bits right

Write status value plus one

How do I update the write status value when the write lock is reentrant? We know that the lower 16 bits of the state value represent the write state, so each increment of the write state is equivalent to the following binary addition.

0000 0000 0000 0000 | 0000 0000 0000 0011
Copy the code
0000 0000 0000 0000 | 0000 0000 0000 0001
Copy the code

Add available

0000 0000 0000 0000 | 0000 0000 0000 0100
Copy the code

So the write state goes from 3 plus 1 to 4; So for write state increase by 1, that is, synchronization state value S + 1.

Read status value plus one

Read state increases by one, same as write state; The synchronization state is S + (1 << 16) because the high 16 bits indicate the read state.

structure

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

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

WriterLock () – write locks

public ReentrantReadWriteLock.WriteLock writeLock(a) {
  return writerLock;
}
Copy the code
Acquiring a lock

When writelock.lock () is executed, sync.acquire() is actually called as follows:

public void lock(a) {
    // Write locks are in exclusive mode
    sync.acquire(1);
}
Copy the code

Sync’s tryAcquire implementation is as follows:

protected final boolean tryAcquire(int acquires) {
      /* * Walkthrough: * 1. If read count nonzero or write count nonzero * and owner is a different thread, fail. * 2. If count would saturate, fail. (This can only * happen if count is already nonzero.) * 3. Otherwise, this thread is eligible for lock if * it is either a reentrant acquire or * queue policy allows it. If so, update state * and set owner. */
      Thread current = Thread.currentThread();
      // Get the synchronization status value
      int c = getState();
      // Get the write status value
      int w = exclusiveCount(c);
      if(c ! =0) {
          // (Note: if c ! = 0 and w == 0 then shared count ! = 0)
          // c ! = 0 indicates that read, write, or read/write operations have been performed
          // If w == 0, the write lock fails to be obtained
          / / if w! = 0 indicates that a write operation has been performed
          / / if the current! = getExclusiveOwnerThread() indicates that the thread currently acquiring the write lock is not the owner of the write lock object, and reentrant fails
          if (w == 0|| current ! = getExclusiveOwnerThread())return false;
          // If the number of reentries exceeds the maximum, an exception is thrown
          if (w + exclusiveCount(acquires) > MAX_COUNT)
              throw new Error("Maximum lock count exceeded");
          // Reentrant acquire
          // Set the status value
          setState(c + acquires);
          return true;
      }
      if(writerShouldBlock() || ! compareAndSetState(c, c + acquires))return false;
      // Set the current thread as the holder of the write lock
      setExclusiveOwnerThread(current);
      return true;
  }
Copy the code

From the implementation of the code and what is described in the comments, it is known that the following scenarios will catch a write lock failure:

  • If a read operation has been performed, the write lock fails to be obtained
  • Failed to acquire the write lock (also a reentrant failure) if the current thread does not own the write lock object.
  • If the CAS fails to update the status value because no operation is performed, the write lock fails to be obtained
Release the lock
protected final boolean tryRelease(int releases) {
    // Determine whether the current thread is the holder of the write lock object
    if(! isHeldExclusively())throw new IllegalMonitorStateException();
    // Release the status value
    int nextc = getState() - releases;
    // Check whether the write status is 0; If the write status is 0, the write lock is released
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        // Clear the write lock holder
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}
Copy the code

ReadLock () – read lock

public ReentrantReadWriteLock.ReadLock  readLock(a)  {
  return readerLock;
}
Copy the code
Acquiring a lock
public void lock(a) {
    sync.acquireShared(1);
}
Copy the code

Because the read/write lock supports multiple threads acquiring the read lock at the same time, sync shared access to the synchronization state is called. Here for the read lock acquisition and release we simplify the implementation of ignoring the read lock count statistics operation.

protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
   
    for (;;) {
        // Get the read status value
        int c = getState();
        // exclusiveCount(c) ! If = 0, write operations are performed
        // getExclusiveOwnerThread ! = current Indicates that the current thread is not the object holder of the write lock. Fails to obtain the read lock
        If getExclusiveOwnerThread == current, the thread can continue to acquire the read lock after acquiring the write lock
        if(exclusiveCount(c) ! =0) {
            if(getExclusiveOwnerThread() ! = current)return -1;
            // else we hold the exclusive lock; blocking here
            // would cause deadlock.
        }
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            // Ignore the read lock count operation
            return 1; }}}Copy the code
Release the lock
protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    // Ignore the read lock count operation
    for (;;) {
        int c = getState();
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
            // Releasing the read lock has no effect on readers,
            // but it may allow waiting writers to proceed if
            // both read and write locks are now free.
            return nextc == 0; }}Copy the code

summary

  • The key to realize read/write lock is how to record the read/write state through an int value. (Using bitwise cutting, high 16 bits for read state, low 16 bits for state)
  • When can read locks be acquired? The thread that acquires the write lock can acquire the read lock again. If the number of threads acquiring the read lock does not exceed 2^ 16-1, the thread can acquire the read lock again.
  • When can write locks be acquired? A write lock cannot be acquired if a read lock is already in operation