ReentrantReadWriteLock блокировки Java

Java задняя часть исходный код

Введение

ПредыдущийReentrantLock из Java Lock (2)Проанализировали синхронизатор AQS, реализованный ReentrantLock. Благодаря анализу исходного кода AQS мы знаем, что синхронизатор получает и освобождает блокировки через состояние состояния и в то же время создает двусторонний связанный список FIFO для ожидания узлов потока. Узлы потоков оценивают свои потребности с помощью состояния ожидания, приостановки или пробуждения для получения блокировки. Далее продолжаем анализировать блокировку чтения-записи ReentrantLock, блокировку ReentrantReadWriteLock.

2. Обзор ReentrantReadWriteLock

Блокировка ReentrantReadWriteLock фактически наследует класс AQS для реализации функции блокировки.ReentrantLock из Java Lock (2)Подробно разобрана реализация AQS.Если вы освоили принцип AQS, я считаю, что разбор следующей блокировки чтения-записи также очень прост.

  • Список внутренних классов блокировки ReentrantReadWriteLock
своего рода эффект
синхронизировать, Наследование AQS, основного исполнителя функции блокировки
FairSync Наследовать синхронизацию, в основном для обеспечения справедливой блокировки
NofairSync Наследовать синхронизацию, в основном для достижения несправедливой блокировки
ReadLock Прочтите блокировку, выполните функцию блокировки с помощью агента SYNC.
WriteLock Блокировка записи, реализация функции блокировки через прокси-сервер синхронизации

Давайте сначала проанализируем четыре константы int в блокировке для чтения-записи. Фактически, функция этих четырех констант состоит в том, чтобы различать старшие 16 бит и младшие 16 бит целого числа int. Блокировка ReentrantReadWriteLock по-прежнему зависит от переменной состояния как стандарт для получения блокировки, тогда как переменная состояния различает блокировки чтения и блокировки записи? Ответ заключается в побитовой операции, старшие 16 бит представляют блокировку чтения, а младшие 16 бит представляют блокировку записи. Если вы не знакомы или не знаете о битовых операциях, вы можете прочитать эту статью«Битовые операции». Поскольку это анализ блокировки чтения-записи, давайте начнем с получения исходного кода блокировки чтения и записи.

Вот концепция, которую нужно добавить заранее:

Блокировка записи и блокировка чтения являются взаимоисключающими (взаимное исключение здесь относится к взаимному исключению между потоками, текущий поток может получить блокировку записи и блокировку чтения, но блокировка чтения не может продолжать получать блокировку записи), это это связано с тем, что блокировки чтения-записи должны поддерживать видимость операций записи.Если блокировке чтения разрешено получать блокировку записи, когда она получена, другие запущенные потоки чтения не могут воспринимать операцию текущего потока записи. Следовательно, блокировка записи может быть получена только текущим потоком до тех пор, пока другие потоки не снимут блокировку чтения, и как только блокировка записи будет получена, последующий доступ других потоков чтения и записи будет заблокирован.

  • записать блокировку tryLock()

В соответствии с отношением вызова внутреннего класса WriteLock мы нашли исходный код следующим образом и обнаружили, что окончательный вызов блокировки записиtryWriteLock()(В качестве примера возьмем метод получения неблокирующей блокировки)

 public boolean tryLock( ) {
            return sync.tryWriteLock();
        }
        
        
 final boolean tryWriteLock() {
            Thread current = Thread.currentThread();
            int c = getState();
            if (c != 0) {//状态不等于0,说明已经锁已经被获取过了
                int w = exclusiveCount(c);//这里是判断是否获取到了写锁,后面会详细分析这段代码
                // 这里就是判断是否是锁重入:2种情况
                // 1.c!=0说明是有锁被获取的,那么w==0,
                // 说明写锁是没有被获取,也就是说读锁被获取了,由于写锁和读锁的互斥,为了保证数据的可见性
                // 所以return false.
                //2. w!=0,写锁被获取了,但是current != getExclusiveOwnerThread() ,
                // 说明是被别的线程获取了,return false;
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w == MAX_COUNT)//判断是否溢出
                    throw new Error("Maximum lock count exceeded");
            }
            // 尝试获取锁
            if (!compareAndSetState(c, c + 1))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }
  • чтение блокировки tryLock()Точно так же мы сначала анализируем метод получения неблокирующей блокировки,tryReadLock()
 final boolean tryReadLock() {
            Thread current = Thread.currentThread();
            for (;;) {
                int c = getState();
                if (exclusiveCount(c) != 0 &&
                    getExclusiveOwnerThread() != current)
                    return false; //写锁被其他线程获取了,直接返回false
                int r = sharedCount(c); //获取读锁的状态
                if (r == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                if (compareAndSetState(c, c + SHARED_UNIT)) { //尝试获取读锁
                    if (r == 0) { //说明第一个获取到了读锁
                        firstReader = current; //标记下当前线程是第一个获取的
                        firstReaderHoldCount = 1; //重入次数
                    } else if (firstReader == current) {
                        firstReaderHoldCount++; //次数+1
                    } else {
                        //cachedHoldCounter 为缓存最后一个获取锁的线程
                        HoldCounter rh = cachedHoldCounter;
                        if (rh == null || rh.tid != getThreadId(current))
                            cachedHoldCounter = rh = readHolds.get(); //缓存最后一个获取锁的线程
                        else if (rh.count == 0)// 当前线程获取到了锁,但是重入次数为0,那么把当前线程存入进去
                            readHolds.set(rh);
                        rh.count++;
                    }
                    return true;
                }
            }
        }
  • Чтение снятия блокировки tryReleaseShared()

Освобождение блокировки записи относительно просто, и основная логика такая же, как и снятие блокировки чтения.Учитывая пространство, на этот раз мы в основном анализируем процесс освобождения блокировки чтения:

 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();  //释放完毕,那么久把保存的记录次数remove掉
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                --rh.count;
            }
            for (;;) {
                int c = getState();
                 // nextc 是 state 高 16 位减 1 后的值
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc)) //CAS设置状态
                    
                    return nextc == 0; //这个判断如果高 16 位减 1 后的值==0,那么就是读状态和写状态都释放了
            }
        }

Вышеприведенный исходный код процесса приобретения и освобождения блокировки чтения-записи.Сначала проанализируйте простой метод получения неблокирующей блокировки.Согласно исходному коду, мы можем узнать, являются ли блокировка записи и блокировка чтения Приобретено также для определения того, не равно ли состояние 0. Метод получения состояния блокировки записи ДаexclusiveCount(c), метод получения состояния блокировки чтенияsharedCount(c). Затем давайте проанализируем, как эти два метода получают свои соответствующие состояния для операции с унифицированными переменными битами Прежде чем анализировать, давайте подытожим предыдущее содержание.

  • Резюме

А. Блокировка чтения-записи зависит от битовой операции переменной State AQS, чтобы отличить блокировку чтения от блокировки записи.Старшие 16 бит представляют блокировку чтения, а младшие 16 бит представляют блокировку записи.

б. Чтобы обеспечить видимость содержимого между потоками, блокировка чтения и блокировка записи являются взаимоисключающими. Взаимное исключение здесь относится к взаимному исключению между потоками. Текущий поток может получить блокировку записи и блокировку чтения, но блокировка чтения получена Блокировка не может продолжать получать блокировку записи.

3. Анализ битовой операции синхронизатора синхронизации

  • Схематическая диаграмма переменных состояния, разделенных по битам

Давайте посмотрим на соответствующий код для битовых операций (полагаю, вы уже знакомы с основами битовых операций, если нет, прочтите«Битовые операции»)

        static final int SHARED_SHIFT   = 16;
        //实际是65536
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
        //最大值 65535
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
        // 同样是65535
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

        /** 获取读的状态  */
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
        /** 获取写锁的获取状态 */
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

Мы работаем в соответствии с данными, показанными на рисунке 32-битные двоичные данные, показанные на рисунке:00000000000000100000000000000011

  • Получить статус чтения

00000000000000100000000000000011 >>> 16, беззнаковый сдвиг вправо на 16 бит, результат будет следующим:00000000000000000000000000000010, Преобразуется в десятичное число, равное 2, что указывает на то, что статус чтения: 2

  • Получить статус чтения

00000000000000100000000000000011 & 65535, преобразованный в двоичную операцию как00000000000000100000000000000011 & 00000000000000001111111111111111

Окончательный результат операции И:00000000000000100000000000000011, преобразуется в десятичную до 3

Я должен восхищаться идеей автора.Этот дизайн обеспечивает разделение блокировок чтения и блокировок записи только через исходную переменную State без изменения кода AQS.

4. Блокировка перехода на более раннюю версию

Понижение блокировки означает, что блокировка записи понижается до блокировки чтения. Если текущий поток удерживает блокировку записи, затем освобождает ее и, наконец, получает блокировку чтения, этот сегментированный процесс нельзя назвать понижением уровня блокировки. Понижение блокировки относится к удержанию (процесс записи блокировок, которыми ранее владел) пример исходного кода (из «Искусства параллельного программирования на Java»):

public void processData(){
    readLock.lock();
    if(!update){
        //必须先释放读锁
        readLock.unlock();
        //锁降级从写锁获取到开始
        writeLock.lock();
        try{
            if(!update){
                update =true;
            }
            readlock.lock();
        }finally{
            writeLock.unlock();
        }//锁降级完成,写锁降级为读锁
    }
    try{
        //略
    }finally{
        readLock.unlock();
    }
}

Приведенный выше пример представляет собой процесс понижения блокировки.Следует отметить, что переменная обновления является изменяемой переменной, поэтому она видна между потоками. Этот код изменяет переменную после получения блокировки записи, затем получает блокировку чтения, освобождает блокировку записи после успешного получения и завершает понижение уровня блокировки. Примечание: ReentrantReadWriteLock не поддерживает эскалацию блокировки, потому что, если несколько потоков получают блокировку чтения, любой из них получает блокировку записи и изменяет данные, а другие потоки не могут воспринимать обновление данных, поэтому видимость данных не может быть гарантирован секс.

окончательное резюме

  • В исходном коде задействованы другие части, которые упрощены в этой статье, такие как:cachedHoldCounter,firstReader firstReaderHoldCountи другие атрибуты, эти атрибуты не имеют большого влияния на понимание принципа, в основном для повышения производительности, поэтому в этой статье не обсуждаются.
  • Блокировка чтения-записи по-прежнему реализуется с помощью пользовательского синхронизатора AQS. Большая часть кода в ней похожа на две статьи «ReentrantLock of Java Lock», проанализированные ранее. Большая часть анализа AQS была проанализирована в этих двух Теперь, если читатели все еще сомневаются в этом, они могут взглянуть на эти две статьи.
  • Умная конструкция блокировки чтения-записи заключается в выполнении операций над состоянием блокировки AQS, различая состояние чтения и состояние записи.