The principle of read-write lock ReadWriteLock

Fanger Wei code or search public micro-channel number of the next scan 菜鸟飞呀飞, you can focus on micro-channel public number, read more Spring源码分析, and Java并发编程articles.

Micro-channel public number

problem

Before reading this article you may first think about a few questions

    1. What is the read-write lock?
    1. What is the meaning of existence ReadWriteLock that?
    1. Write lock apply to what the scene?
    1. What is the lock and lock downgrade upgrade?

Brief introduction

  • synchronized and ReentrantLock lock is achieved 排他锁, the so-called exclusive lock is the same time allowing only one thread to access shared resources, but in peacetime scenario, we often encounter shared resources 读多写少scene. For reading the scene, allowing only one thread to access shared resources, it is clear that using an exclusive lock efficiency is relatively low, then how to optimize it?
  • This time 读写锁it came into being, read-write locks is a common technique, not Java-specific. From the name of view, read and write lock has two locks, 读锁and 写锁. Read-write lock feature is: the same time allows multiple threads to shared resources read operation; the same time allowing only one thread writes to shared resources; when a write operation, the same time other threads read operation will be blocked; when the read operation, write the same time all the threads will be blocked. For a read lock, since the same time can allow multiple threads to access shared resources, read, and therefore called shared lock; and for the write lock, the same time allowing only one thread to access shared resources, write, So call it an exclusive lock.
  • In Java, through ReadWriteLockto read and write locks. ReadWriteLock is an interface, ReentrantReadWriteLockis ReadWriteLock interface implementation class. ReentrantReadWriteLock defined in the two internal classes ReadLock, WriteLockrespectively to implement the read and write locks. ReentrantReadWriteLock underlying AQS is accomplished by the lock acquisition and release, the internal ReentrantReadWriteLock also defines a synchronization component AQS inherited class Sync, while also ReentrantReadWriteLock 支持公平与非公平性, so it also defines inside two internal classes FairSync、NonfairSyncthat inherit Sync.
  • ReentrantReadWriteLock In addition to providing a read lock, write lock release and access, but also provides a number of other state and lock-related methods. As shown in the following table (table from "the Java programming Concurrent Art" on page 141).
Method name Features
int getReadLockCount() Acquiring a read lock number, then the number of read lock is not necessarily equal to the number of locks acquired, since the lock can be reentrant, reentrant thread may have a read lock
int getReadHoldCount() Gets the number of times the current thread lock re-enroll
int getWriteHoldCount() Gets the number of times the current thread to write reentrant lock
int isWriteLocked() To determine whether the state of the lock is a write lock, it returns true, indicating that the lock is a write lock state

The principle

  • In AQS by int类型the global variable to represent the state of the synchronization state, i.e., represented by the lock state. ReentrantReadWriteLock also be achieved through the lock AQS, but ReentrantReadWriteLock have two locks: read and write locks, which are protected resources are the same, then how to use a shared variable to distinguish between the lock is a write lock or read lock it? The answer is 按位拆分.
  • Since the state is a variable of type int, in memory 占用4个字节,也就是32位. Which was split into two parts: the upper 16 bits and lower 16 bits, wherein 高16位用来表示读锁状态,低16位用来表示写锁状态. When the read lock is successfully set, plus 1 16-bit will be high, read lock is released, the upper 16 bits minus 1; When setting a write lock successful, it will be the lower 16 bits plus 1, the write lock is released, the first 16 subtract 1. As shown below.

Read-Write Lock

  • So write lock or read lock it how to determine the current state of the lock according to the value of the state?
  • Assuming the lock is the current state S, S and the hexadecimal 0x0000FFFFperformed 与运算, i.e., S & 0x0000FFFF, when calculating the upper 16 bits will be set to all 0, will be referred to as the operation result c, then the number of write locks is represented by c. If c is equal to 0 means that no thread acquires the lock; if c is not equal to 0, it means there is a thread to acquire the lock, c is equal to a few representatives to write reentrant lock a few times.
  • The S 无符号右移16位(S >>> 16), the result is obtained 读锁的数量. When the result of S >>> for 16 is not equal to 0, and c is not equal to 0, it means both the current thread holds a write lock, also holds a read lock.
  • When the lock is acquired successfully read, how to read it locks plus 1? Results S + (1 << 16) obtained, that will add a lock. Releasing the read lock is performed on S - (1 << 16) operation.
  • When the write lock is acquired successfully, so that S + 1 + 1 means that the write lock state; releasing the write lock, operation proceeds S-1.
  • Since the read and write locks only the state value occupies 16 bits, the maximum number of read locks is 2^{16}-1, the maximum number of write locks may be reentrant is 2^{16}-1.

Source code analysis

Understand how the state is represented by the state of the lock, then the source code by analyzing source code to achieve read-write lock.

  • By the following code, you can create read and write locks. If the constructor ReentrantReadWriteLock not pass parameters 默认创建的是非公平的读写锁. In the read-write lock remains 非公平的读写锁性能要由于公平的读写锁.
ReadWriteLock lock = new ReentrantReadWriteLock();
// 创建读锁
Lock readLock = lock.readLock();
// 创建写锁
Lock writeLock = lock.writeLock();	
复制代码

Write lock lock

  • When you call a write lock the lock () method, the thread tries to acquire a write lock that writeLock.lock (). Since the write lock is an exclusive lock, so write lock acquisition process to acquire locks almost as logical and ReentrantLock. When calling lock () method will be called first to the AQS acquire () method, acquire () method will first call tryAcquire subclass () method, so here called internal type Sync ReentrantReadWriteLock with the tryAcquire () method. The source of the method are as follows.
protected final boolean tryAcquire(int acquires) {
    
    Thread current = Thread.currentThread();
    int c = getState();
    // exclusiveCount()方法的作用是将同步变量与0xFFFF做&运算,计算结果就是写锁的数量。
    // 因此w的值的含义就是写锁的数量
    int w = exclusiveCount(c);
    // 如果c不为0就表示锁被占用了,但是占用的是写锁还是读书呢?这个时候就需要根据w的值来判断了。
    // 如果c等于0就表示此时锁还没有被任何线程占用,那就让线程直接去尝试获取锁
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
        //
        /**
         * 1. 如果w为0,说明写锁的数量为0,而此时又因为c不等于0,说明锁被占用,但是不是写锁,那么此时锁的状态一定是读锁,
         * 既然是读锁状态,那么写锁此时来获取锁时,就肯定失败,因此当w等于0时,tryAcquire()方法返回false。
         * 2. 如果w不为0,说明此时锁的状态时写锁,接着进行current != getExclusiveOwnerThread()判断,判断持有锁的线程是否是当前线程
         * 如果不是当前线程,那么tryAcquire()返回false;如果是当前线程,那么就进行后面的逻辑。为什么是当前线程持有锁,就还能执行后面的逻辑呢?
         * 因为读写锁是支持重入的。
         */
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        // 下面一行代码是判断,写锁的重入次数或不会超过最大限制,这个最大限制是:2的16次方减1
        // 为什么是2的16次方减1呢?因为state的低16位存放的是写锁,因此写锁数量的最大值是2的16次方减1
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        setState(c + acquires);
        return true;
    }
    /**
     * 1. writerShouldBlock()方法的作用是判断当前线程是否应该阻塞,对于公平的写锁和非公平写锁的具体实现不一样。
     * 对于非公平写锁而言,直接返回false,因为非公平锁获取锁之前不需要去判断是否排队
     * 对于公平锁写锁而言,它会判断同步队列中是否有人在排队,有人排队,就返回true,表示当前线程需要阻塞。无人排队就返回false。
     *
     * 2. 当writerShouldBlock()返回true时,表示当前线程还不能直接获取锁,因此tryAcquire()方法直接返回false。
     * 当writerShouldBlock()返回false时,表示当前线程可以尝试去获取锁,因此会执行if判断中后面的逻辑,即通过CAS方法尝试去修改同步变量的值,
     * 如果修改同步变量成功,则表示当前线程获取到了锁,最终tryAcquire()方法会返回true。如果修改失败,那么tryAcquire()会返回false,表示获取锁失败。
     *
     */
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}
复制代码
  • In tryAcquire () method, by first exclusiveCount()calculate the number of write locks, how to calculate the way to do that? And the state is 0x0000FFFFcarried out 与运算.
  • Then determine state is equal to 0, if equal to 0, it means to read and write locks are not acquired by the current thread is called writerShouldBlock()if the method determines the thread to wait, if you need to wait, tryAcquire () method returns false, indicating acquire the lock failure , it will return to the AQS acquire () method, the same logic as the exclusive lock behind. If you do not wait, try to modify the value of the state, if the modification is successful, it means acquiring the lock success, otherwise fail.
  • If the state is not equal to 0, it means that there is a read lock or write lock, then what is read lock or a write lock it? The judgment on the need for the value of w.
  • If w is 0, indicating that the number of write lock is 0, but this time also because c is not equal to 0, indicating that the lock is occupied, but not a write lock, then the time state of the lock must be read lock, since it is a read lock state, so this time when write locks to acquire the lock, it will certainly fail, because there is a read lock, can not go get the write lock. Therefore, when w is equal to 0, tryAcquire () method returns false.
  • If w is not zero, indicating that at this time the lock state is write lock, followed by current != getExclusiveOwnerThread()judgment as to whether the thread holding the lock is the current thread. If it is not the current thread, then tryAcquire () return false; if the current thread, then the logic behind. Why is the current thread holds the lock, you can perform logic behind it? Because the read-write lock is to support re-entry.
  • If the current thread is acquired write lock, and then they judge, again write reentrant locks, will not exceed the maximum number of write reentrant lock, if it is throwing an exception. (Because of the low 16-bit write lock state representation, so that the maximum number of write locks may be reentrant is 2^{16}-1).

Write lock release

  • Write lock release and the release of the exclusive lock logic is almost the same. When calling writeLock.unlock (), the first call to release AQS () method, the release () method will first call tryRelease () method subclass. Here is the call of the inner class Sync ReentrantReadWriteLock of tryRelease () method. Write lock release logic is simple, you can refer to the following comments in the source code. Source and comments following method.
protected final boolean tryRelease(int releases) {
    // 判断是否是当前线程持有锁
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    // 将state的值减去releases
    int nextc = getState() - releases;
    // 调用exclusiveCount()方法,计算写锁的数量。如果写锁的数量为0,表示写锁被完全释放,此时将AQS的exclusiveOwnerThread属性置为null
    // 并返回free标识,表示写锁是否被完全释放
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}
复制代码

Read lock lock

  • Read lock is a shared lock, so when you call readLock.lock () method, will first call to the acquiredShared AQS () method, will first call tryAcquireShared subclass () method acquireShared () method. Here is ReentrantReadWriteLock calls inside the class Sync tryAcquireShared()method. The source of the method are as follows.
protected final int tryAcquireShared(int unused) {
    
    Thread current = Thread.currentThread();
    int c = getState();
    // exclusiveCount(c)返回的是写锁的数量,如果它不为0,说明写锁被占用,如果此时占用写锁的线程不是当前线程,就返回-1,表示获取锁失败
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    // r表示的是读锁的数量
    int r = sharedCount(c);
    /**
     * 在下面的代码中进行了三个判断:
     * 1、读锁是否应该排队。如果没有人排队,就进行if后面的判断。有人排队,就不会进行if后面的判断,而是最终调用fullTryAcquireShared()方法
     * 2、读锁数量是否超过最大值。(最大数量为2的16次方-1)
     * 3、尝试修改同步变量的值
     */
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        // 读锁数量为0时,就将当前线程设置为firstReader,firstReaderHoldCount=1
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            // 读锁数量不为0且firstReader(第一次获取读的线程)为当前线程,就将firstReaderHoldCount累加
            firstReaderHoldCount++;
        } else {
            // 读锁数量不为0,且第一个获取到读锁的线程不是当前线程
            // 下面这一段逻辑就是保存当前线程获取读锁的次数,如何保存的呢?
            // 通过ThreadLocal来实现的,readHolds就是一个ThreadLocal的实例
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        // 返回1表示获取读锁成功
        return 1;
    }
    // 当if中的三个判断均不满足时,就会执行到这儿,调用fullTryAcquireShared()方法尝试获取锁
    return fullTryAcquireShared(current);
}
复制代码
  • In tryAcquireShared () method, will first pass exclusiveCount()calculates the number of write lock method, if there is a write lock, then judge holds a write lock thread is not the current thread, if not the current thread, it means write lock to be occupied by other threads At this time the current thread can not get a read lock. tryAcquireShared () method returns -1, acquires a read lock fail. If the write lock does not exist or has the write lock thread is the current thread, it means that the current thread to have the opportunity to acquire a read lock.
  • Took over will determine whether the current thread to obtain a read lock does not need to line up, whether or not read more than the maximum number of locks, and whether to modify the state of a read lock successful (the value of the state plus 1 << 16) by CAS. If these three conditions are true, the if statement to enter the block, this piece of code is more complicated, but relatively simple function, that is, the number of locks and read statistics on the number of times the current thread re-enroll in the lock, the underlying principle is ThreadLocal. Since the write lock is provided in the getReadLockCount()、getReadHoldCount()other methods, these data several ways from here on.
  • If the three conditions above have an established, would not have entered if block, it will call fullTryAcquireShared () method. The role of this method is to let the thread stop acquiring a lock, its source code is as follows.
final int fullTryAcquireShared(Thread current) {
    /*
     * This code is in part redundant with that in
     * tryAcquireShared but is simpler overall by not
     * complicating tryAcquireShared with interactions between
     * retries and lazily reading hold counts.
     */
    HoldCounter rh = null;
    // for死循环,直到满足相应的条件才会return退出,否则一直循环
    for (;;) {
        int c = getState();
        // 锁的状态为写锁时,持有锁的线程不等于当期那线程,就说明当前线程获取锁失败,返回-1
        if (exclusiveCount(c) != 0) {
            if (getExclusiveOwnerThread() != current)
                return -1;
            // else we hold the exclusive lock; blocking here
            // would cause deadlock.
        } else if (readerShouldBlock()) {
            // Make sure we're not acquiring read lock reentrantly
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
            } else {
                if (rh == null) {
                    rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current)) {
                        rh = readHolds.get();
                        if (rh.count == 0)
                            readHolds.remove();
                    }
                }
                if (rh.count == 0)
                    return -1;
            }
        }
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // 尝试设置同步变量的值,只要设置成功了,就表示当前线程获取到了锁,然后就设置锁的获取次数等相关信息
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            if (sharedCount(c) == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                if (rh == null)
                    rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
                cachedHoldCounter = rh; // cache for release
            }
            return 1;
        }
    }
}
复制代码
  • When to get a read lock successful, tryAcquireShared () method returns 1, so that when the back AQS acquireShared () method, it will directly end. If the failure to acquire the lock, tryAcquireShared () method returns -1, then the AQS will then performed doAcquireShared () method. Role doAcquireShared () method is to add yourself to sync queue, waiting to acquire a lock until the lock acquisition success. This method does not respond to interrupts.

Read lock release

  • When calling readLock.unlock () method will be called first to the AQS releaseShared () method, releaseShared () method will first call tryReleaseShared subclass () method. Here is ReentrantReadWriteLock calls inside the class Sync tryReleaseShared()method. The source of the method are as follows.
protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    if (firstReader == current) {
        // assert firstReaderHoldCount > 0;
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--;
    } else {
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != getThreadId(current))
            rh = readHolds.get();
        int count = rh.count;
        if (count <= 1) {
            readHolds.remove();
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        --rh.count;
    }
    for (;;) {
        int c = getState();
        // 将修改同步变量的值(读锁状态减去1<<16)
        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;
    }
}
复制代码
  • In tryReleaseShared () method, and modifications will first read lock count related data, and then for an endless loop, the CAS operation by subtracting the value of the state 1 << 16. If the CAS operation is successful, it will exit from the for loop. When the number of read lock is 0, tryReleaseShared () returns true, indicates the lock is fully released.
  • When tryReleaseShared () method returns the next logical step and release shared locks exactly the same.

Precautions

  • Read-write lock is very simple, but in the course of read-write lock, you need to pay attention to the following points.
  • 1.Read-write locks 不支持锁升级, 支持锁降级. Lock escalation refers to the thread gets to read lock, under the premise of not releasing the read lock, and acquire write lock. Lock downgrade means that thread gets to write lock, in the absence of the release write lock, and acquire a read lock. Why lock escalation does not support it? You can refer to the following sample code.
public void lockUpgrade(){
    ReadWriteLock lock = new ReentrantReadWriteLock();
    // 创建读锁
    Lock readLock = lock.readLock();
    // 创建写锁
    Lock writeLock = lock.writeLock();
    readLock.lock();
    try{
        // ...处理业务逻辑
        writeLock.lock();   // 代码①
    }finally {
        readLock.unlock();
    }
}
复制代码
  • In the sample code above, if the T1 thread first obtains a read lock, and then execute the code behind, when executing the code ① on the line, T2 thread also to acquire the read lock, since the read lock is a shared lock, and at this time write lock has not been acquired, so in this case T2 thread can acquire the lock to read, when the code to execute when T1 ①, attempt to acquire a write lock, due to take up the thread T2 read lock, so the T1 thread is unable to obtain a write lock can only wait, when T2 is also performed when the code ①, due to the possession of a read lock T1, T2 can not get to lead to a write lock, so two threads have been waiting for, namely not obtain a write lock, but also can not afford to read the release lock. So lock lock escalation is not supported.
  • Read-Write Lock support lock downgrade, lock the downgrade is to ensure visibility. Let T1 thread modifies the data visible to other threads.
  • 2.Read lock queue condition is not supported. When you call newCondition ReadLock class () method, a direct throw.
public Condition newCondition() {
    throw new UnsupportedOperationException();
}
复制代码
  • Since the read lock is a shared lock, the maximum number of times to get 2^{16}-1), the same time can be held by multiple threads for read lock, the other threads waiting to acquire the read lock is not necessary, Condition waiting wake meaningless.

to sum up

  • This article briefly describes the read-write lock function, which consists of two locks: the read and write locks. Then describes some of the characteristics of the read-write lock. Then it analyzes how the state is represented by state-write lock this one variable, high state of 16 indicates a read lock, low 16 for a write lock, and the state will 0x0000FFFFbe 与运算that the number of write lock obtained.
  • Finally, respectively, by analyzing the source code written release lock and get the release process, read lock and acquisition process. Where the write lock is an exclusive lock, so its release and access are called exclusive release the lock and acquire the lock AQS in the way, read lock is a shared lock, so its release and access call is AQS is shared release lock and methods to obtain the lock.

related suggestion

Micro-channel public number

Guess you like

Origin juejin.im/post/5dc22993f265da4cf77c8ded