King Concurrency Course — Platinum 2: Внезапно яркий — «Неясный» ReadWriteLock настолько прекрасен

Java задняя часть
King Concurrency Course — Platinum 2: Внезапно яркий — «Неясный» ReadWriteLock настолько прекрасен

Добро пожаловать в"Королевский класс параллелизма, эта статья является частью серииСтатья 15.

В прошлой статье мы представили базовый интерфейс Lock для блокировок в Java. В этой статье мы представим еще один важный базовый интерфейс блокировок в Java, а именноReadWriteLockинтерфейс.

ReadWriteLock, несомненно, важен при изучении параллелизма в Java, но понять его непросто.. Если вы ранее искали информацию, то должны обнаружить, что в большинстве статей она описывается туманно и непонятно, либо исходный код отображается в виде серии статей, либо в нескольких словах используется несколько слов.

Поэтому в этой статье мы сосредоточимся на понимании идеи, а не интерпретации исходного кода.. Что касается исходного кода и знаний, стоящих за ним, мы объясним его в более поздних, более продвинутых сериях.

1. Поймите значение ReadWriteLock

Чтобы понять ReadWriteLock, первой странице нужно понять, в чем смысл его существования.Другими словами, какую проблему он решает?. С этой целью мы можем начать со следующего рисунка, чтобы выяснить это.

Не знаю, понимаете ли вы это, но эта картинка имеет три значения:

  • Большое количество потоков конкурирует за один и тот же ресурс;
  • Некоторые из этих потоковзапрос на чтение, некоторыенаписать запрос;
  • В запросах из нескольких потоковЗапросы на чтение значительно выше, чем запросы на запись.

Эта сцена кажется знакомой?Да, это типичный сценарий применения кэша.

Как мы все знаем, наличие кеша предназначено для повышения производительности чтения и записи приложения. С одной стороны, нам нужно перехватить большое количество запросов на чтение данных через кеш. С другой стороны, нам также необходимо время от времени обновлять кеш.Но в целом количество обновлений кеша намного меньше, чем количество чтений кеша..

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

Подводя итог, этот вопрос в основном имеет следующие пункты:

  • Данные позволяют нескольким потокам читать одновременно, но только одному потоку для записи.;
  • При чтении данных не может быть операции записи или запроса на запись;
  • При записи данных не может быть запроса на чтение.

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

Пока вы не поймете ReadWriteLock, вы найдете его очень непонятным, а исходный код скучным.Однако, как только вы поймете проблемы, которые он решает, и решения, которые он предлагает, вы обнаружите, что он так умно спроектирован.. На самом деле он спроектировал два совершенно разных замка,Один из них является эксклюзивным для потока, как мы знали ранее, в то время как другой замок может использоваться несколькими потоками.! Идеальное взаимодействие двух замков решает проблему одновременных сценариев чтения и записи.

После внезапного осознания так называемый исходный код — это просто очередь и совместное использование, они являются реализацией ReadWriteLock, а не камнем преткновения, блокирующим ваше понимание.

2. Самостоятельно реализовать ReadWriteLock

Поняв проблему, стоящую за ReadWriteLock, и ее решение, мы можем полностью отказаться от исходного кода в JDK и самостоятельно реализовать блокировку чтения-записи.

public class ReadWriteLock{

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

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

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

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

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

  public synchronized void unlockWrite() throws InterruptedException{
    writers--;
    notifyAll();
  }
}

блокировка чтенияlockRead(), не допускается иметьнаписать запросилиоперация записииз. Если есть, то запрос на чтение будет ждать.

пока вlockWrite()середина,При этом запросы на чтение и другие операции записи не допускаются, и в это время разрешен только один запрос на запись..

Выше приведена простая автономная реализация блокировок чтения-записи.Конечно, это не полный, просто базовый пример. не учитывает основноеповторный вход в потокПроблема, реальная ситуация намного сложнее, чем она есть, но вы просто понимаете, что это значит.

3. Как реализована функция ReadWriteLock в Java

Наконец, давайте рассмотрим некоторые основные идеи реализации ReadWriteLock в JDK. Базовая связь между ReadWriteLock и интерфейсом Lock и другими классами, упомянутыми в предыдущей статье, показана на следующем рисунке:

Видно, что реализация блокировки чтения-записи в JDK находится в стадии разработки.ReentrantReadWriteLockв этом классе. ReentrantReadWriteLock содержит два внутренних класса: ReadLock и WriteLock, которые, в свою очередь, реализуют интерфейс Lock.

Обновление и понижение блокировки чтения-записи

Модернизация и понижение блокировки чтения-записи — важная точка знаний в ReentrantReadWriteLock, а также часто задаваемый вопрос на собеседованиях.

Переход от блокировки чтения к блокировке записи называется обновлением блокировки, и наоборот — понижением уровня блокировки.. Самый интуитивно понятный способ понять обновление и понижение блокировки чтения-записи — это написать проверку кода.

Фрагмент кода 1, сначала получите блокировку чтения, а затем получите блокировку записи.

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        readWriteLock.readLock().lock();
        System.out.println("已经获取读锁...");
        readWriteLock.writeLock().lock();
        System.out.println("已经获取写锁...");
    }
}

Результат выглядит следующим образом:

已经获取读锁...

Фрагмент кода 2, сначала получите блокировку записи, а затем получите блокировку чтения:

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        readWriteLock.writeLock().lock();
        System.out.println("已经获取写锁...");
        readWriteLock.readLock().lock();
        System.out.println("已经获取读锁...");
    }
}

Результат выглядит следующим образом:

已经获取写锁...
已经获取读锁...

Process finished with exit code 0

Таким образом, результат уже очень ясен.ReentrantReadWriteLock поддерживает понижение блокировки, но не обновление блокировки..

Справедливость в блокировках чтения-записи

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

В ReentrantReadWriteLock предусмотрены как честные, так и несправедливые режимы, и по умолчанию используется несправедливый режим.. Из приведенного ниже фрагмента исходного кода это хорошо видно.

 public ReentrantReadWriteLock() {
        this(false);
 }

    /**
   /**
 * Creates a new {@code ReentrantReadWriteLock} with
 * default (nonfair) ordering properties.
 */
public ReentrantReadWriteLock() {
  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);
}

резюме

Это все, что касается блокировок чтения-записи. В этой статье мы начнем с проблемы с кэшем, а затем поищем ответ в ReadWriteLock, чтобы мы могли понять все тонкости ReadWriteLock с более спокойной точки зрения.

Ключом к пониманию ReadWriteLock является не анализ исходного кода, а понимание его идей.

Кроме того, мы кратко представили некоторые ключевые точки знаний в ReentrantReadWriteLock, но не расширили утверждение, такое как AQS, стоящее за ним. Не беспокойтесь об этом, у нас будет подробный анализ и введение позже.

На этом текст окончен, поздравляю с получением очередной звезды✨

Испытание мастера

  • Попробуйте добавить в пример кода поддержку повторного входа для потоков чтения и записи.

Дальнейшее чтение и ссылки

Об авторе

Почти десять лет работал в agile и DevOps консалтинге, техническим руководителем и менеджментом, имеет богатый практический опыт в распределенной архитектуре с высоким параллелизмом. Увлеченный обменом технологиями и переводом книг в конкретных областях, буклет Nuggets "Суть дизайна и реализация всплеска высокой параллелизма"автор.


Подпишитесь на официальный аккаунт [MetaThoughts], чтобы своевременно получать обновления статей и рукописи.

Если эта статья была вам полезна, добро пожаловатькак,обрати внимание на,контролировать,Вместе мы будемОт бронзы до короля.