Есть ли какой-либо приоритет получения WriteLock над ReadLock в ReentrantReadWriteLock - PullRequest
3 голосов
/ 15 октября 2019

Я прочитал документацию по Java: ReentrantReadWriteLock

И я не вижу, чтобы writeLock имел какой-либо приоритет над readLock

Но я также читал такие темы, какthis: Являются ли блокировки чтения и записи в ReentrantReadWriteLock как-то связаны?
и я вижу там в обоих ответах следующие фразы:

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


Предпочитает писателей читателям. То есть, если писатель ожидает блокировки, новые читатели из другого потока не смогут получить доступ к ресурсу. Существующие читатели могут продолжать использовать ресурс, пока не снимают блокировку. Это предотвращает так называемое «писательское голодание».

И эти фразы звучат осмысленно и выглядят так, будто я их уже где-то читал.

Но, очевидно, это противоречит javaдокумент.

Изменился ли он в последней версии JDK? это все еще действует?

Как предотвратить писательский голод?

Ответы [ 3 ]

2 голосов
/ 17 октября 2019

Я создал образец для проверки:

public class RWLockTest {
    public static final Logger LOGGER = LoggerFactory.getLogger(RWLockTest.class);

       public static void main(String[] args) {
        SomeClass someClass = new SomeClass();

        Reader readerRunnable = new Reader(someClass);
        Writer writerRunnable = new Writer(someClass);
        //group 1 readers
        for (int i = 0; i < 10; i++) {
            new Thread(readerRunnable).start();
        }

        // 2 writers
        new Thread(writerRunnable).start();
        LOGGER.info("!!!!!!!!!!!!!!!WRITER_1 WAS STARTED!!!!!!!!!!!!!!!");
        new Thread(writerRunnable).start();
        LOGGER.info("!!!!!!!!!!!!!!!WRITER_2 WAS STARTED!!!!!!!!!!!!!!!");

       //group 2 readers            
       for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(readerRunnable);
            LOGGER.info(String.format("%s was submitted", thread.getId()));
            thread.start();
        }
    }

    public static class SomeClass {
        public ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

        public void read() {
            readWriteLock.readLock().lock();
            try {
                LOGGER.info(String.format("Read by %s started", Thread.currentThread().getId()));
                Thread.sleep(5000);
                LOGGER.info(String.format("Read by %s finished", Thread.currentThread().getId()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                readWriteLock.readLock().unlock();

            }
        }

        public void write() {
            readWriteLock.writeLock().lock();
            try {
                LOGGER.info(String.format("!!!!!!!!!!Write by %s started!!!!!!!!!!!!", Thread.currentThread().getId()));
                Thread.sleep(3000);
                LOGGER.info(String.format("!!!!!!!!!!Write by %s finished!!!!!!!!", Thread.currentThread().getId()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                readWriteLock.writeLock().unlock();

            }
        }

    }

    public static class Reader implements Runnable {
        SomeClass someClass;

        public Reader(SomeClass someClass) {
            this.someClass = someClass;
        }

        @Override
        public void run() {
            someClass.read();
        }
    }

    public static class Writer implements Runnable {
        SomeClass someClass;

        public Writer(SomeClass someClass) {
            this.someClass = someClass;
        }

        @Override
        public void run() {
            someClass.write();
        }
    }
}

Я пытался запустить много времени, и вывод, как правило, выглядит так:

16:31:49.037 [main] INFO my.pack.RWLockTest - !!!!!!!!!!!!!!!WRITER_1 WAS STARTED!!!!!!!!!!!!!!!
16:31:49.040 [main] INFO my.pack.RWLockTest - !!!!!!!!!!!!!!!WRITER_2 WAS STARTED!!!!!!!!!!!!!!!
16:31:49.046 [Thread-1] INFO my.pack.RWLockTest - Read by 13 started
16:31:49.046 [main] INFO my.pack.RWLockTest - 24 was submitted
16:31:49.046 [Thread-7] INFO my.pack.RWLockTest - Read by 19 started
16:31:49.046 [Thread-4] INFO my.pack.RWLockTest - Read by 16 started
16:31:49.046 [Thread-5] INFO my.pack.RWLockTest - Read by 17 started
16:31:49.046 [Thread-0] INFO my.pack.RWLockTest - Read by 12 started
16:31:49.046 [Thread-3] INFO my.pack.RWLockTest - Read by 15 started
16:31:49.047 [main] INFO my.pack.RWLockTest - 25 was submitted
16:31:49.046 [Thread-9] INFO my.pack.RWLockTest - Read by 21 started
16:31:49.047 [Thread-8] INFO my.pack.RWLockTest - Read by 20 started
16:31:49.047 [main] INFO my.pack.RWLockTest - 26 was submitted
16:31:49.047 [Thread-2] INFO my.pack.RWLockTest - Read by 14 started
16:31:49.047 [Thread-6] INFO my.pack.RWLockTest - Read by 18 started
16:31:49.047 [main] INFO my.pack.RWLockTest - 27 was submitted
16:31:49.048 [main] INFO my.pack.RWLockTest - 28 was submitted
16:31:49.048 [main] INFO my.pack.RWLockTest - 29 was submitted
16:31:49.048 [main] INFO my.pack.RWLockTest - 30 was submitted
16:31:49.048 [main] INFO my.pack.RWLockTest - 31 was submitted
16:31:49.049 [main] INFO my.pack.RWLockTest - 32 was submitted
16:31:49.049 [main] INFO my.pack.RWLockTest - 33 was submitted
16:31:54.047 [Thread-7] INFO my.pack.RWLockTest - Read by 19 finished
16:31:54.048 [Thread-6] INFO my.pack.RWLockTest - Read by 18 finished
16:31:54.047 [Thread-5] INFO my.pack.RWLockTest - Read by 17 finished
16:31:54.049 [Thread-2] INFO my.pack.RWLockTest - Read by 14 finished
16:31:54.051 [Thread-8] INFO my.pack.RWLockTest - Read by 20 finished
16:31:54.047 [Thread-1] INFO my.pack.RWLockTest - Read by 13 finished
16:31:54.050 [Thread-9] INFO my.pack.RWLockTest - Read by 21 finished
16:31:54.049 [Thread-4] INFO my.pack.RWLockTest - Read by 16 finished
16:31:54.049 [Thread-3] INFO my.pack.RWLockTest - Read by 15 finished
16:31:54.049 [Thread-0] INFO my.pack.RWLockTest - Read by 12 finished
16:31:54.057 [Thread-10] INFO my.pack.RWLockTest - !!!!!!!!!!Write by 22 started!!!!!!!!!!!!
16:31:57.057 [Thread-10] INFO my.pack.RWLockTest - !!!!!!!!!!Write by 22 finished!!!!!!!!
16:31:57.058 [Thread-11] INFO my.pack.RWLockTest - !!!!!!!!!!Write by 23 started!!!!!!!!!!!!
16:32:00.060 [Thread-11] INFO my.pack.RWLockTest - !!!!!!!!!!Write by 23 finished!!!!!!!!
16:32:00.061 [Thread-13] INFO my.pack.RWLockTest - Read by 25 started
16:32:00.061 [Thread-14] INFO my.pack.RWLockTest - Read by 26 started
16:32:00.061 [Thread-12] INFO my.pack.RWLockTest - Read by 24 started
16:32:00.061 [Thread-15] INFO my.pack.RWLockTest - Read by 27 started
16:32:00.061 [Thread-17] INFO my.pack.RWLockTest - Read by 29 started
16:32:00.062 [Thread-19] INFO my.pack.RWLockTest - Read by 31 started
16:32:00.062 [Thread-18] INFO my.pack.RWLockTest - Read by 30 started
16:32:00.061 [Thread-16] INFO my.pack.RWLockTest - Read by 28 started
16:32:00.062 [Thread-20] INFO my.pack.RWLockTest - Read by 32 started
16:32:00.062 [Thread-21] INFO my.pack.RWLockTest - Read by 33 started
16:32:05.060 [Thread-12] INFO my.pack.RWLockTest - Read by 24 finished
16:32:05.060 [Thread-15] INFO my.pack.RWLockTest - Read by 27 finished
16:32:05.060 [Thread-13] INFO my.pack.RWLockTest - Read by 25 finished
16:32:05.060 [Thread-17] INFO my.pack.RWLockTest - Read by 29 finished
16:32:05.060 [Thread-14] INFO my.pack.RWLockTest - Read by 26 finished
16:32:05.062 [Thread-21] INFO my.pack.RWLockTest - Read by 33 finished
16:32:05.062 [Thread-16] INFO my.pack.RWLockTest - Read by 28 finished
16:32:05.062 [Thread-19] INFO my.pack.RWLockTest - Read by 31 finished
16:32:05.062 [Thread-18] INFO my.pack.RWLockTest - Read by 30 finished
16:32:05.062 [Thread-20] INFO my.pack.RWLockTest - Read by 32 finished

Что это делаетзначит?

Как видите, я делаю следующее:

  1. запустить 10 потоков для чтения. все 10 потоков могут работать параллельно. Каждое чтение занимает 5 секунд
  2. Затем я запускаю 2 писателя (1 запись занимает 3 секунды)
  3. Затем я запускаю 10 считывателей

Как вы видите задержку междупервая запись была отправлена ​​(16:31:49.037 [main] INFO my.pack.RWLockTest - !!!!!!!!!!!!!!!WRITER_1 WAS STARTED!!!!!!!!!!!!!!!), а когда она фактически указана ((полученная блокировка) 16:31:54.057 [Thread-10] INFO my.pack.RWLockTest - !!!!!!!!!!Write by 22 started!!!!!!!!!!!!), составляет 5 секунд. Это время чтения.

Также вы можете видеть, что после того, как 2 потока писателей были отправлены, мы отправили 10 потоков читателей с идентификаторами от 24 до 33 , и все они были фактически запущены (полученыблокировок) после того, как авторский поток закончил свои работы.

Таким образом, потоки читателей с идентификаторами от 24 до 33 были представлены в 16:31:49, и в это время readlock был получен первыми 10 читателями и выглядит какони также смогли получить блокировку чтения, но, похоже, внутри ReentrantReadWriteLock есть что-то, что мешает избежать голодания писателя. В конце концов вторая группа читателей смогла получить блокировку только в 16:32:00 (через 6 секунд после отправки)

Я не знаю, гарантировано ли это, но из моих тестов это всегда работает таким образом. Таким образом, у нас есть некоторый приоритет Writers над Readers , хотя java doc говорит:

Этот класс не навязывает порядок чтения или записи для доступа к блокировке

2 голосов
/ 15 октября 2019

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

0 голосов
/ 23 октября 2019

Окончательный ответ всегда находится в коде, поэтому давайте посмотрим там.

Вот конструктор (примечание: конструктор по умолчанию вызывает его с установкой fair false)

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

Таким образом, единственное различие заключается в том, содержит ли атрибут sync экземпляр FairSync или NonfairSync. Чем отличаются эти реализации?

Вот код из writerShouldBlock метода FairSync класса:

final boolean writerShouldBlock() {
    return hasQueuedPredecessors();
}

, что означает, что «если есть строка», то авторблокирует и попадает в эту строку (очередь). Однако это резко контрастирует с реализацией класса NonfairSync, который:

final boolean writerShouldBlock() {
    return false;
}

, который окончательно показывает, как в non fair mode писатели получают приоритет над читателями.

Один последнийКомментарий относительно писательского голодания. В non fair mode это достигается при реализации сопутствующего метода: readerShouldBlock. Комментарии из кода в классе NonfairSync гласят:

    final boolean readerShouldBlock() {
        /* As a heuristic to avoid indefinite writer starvation,
         * block if the thread that momentarily appears to be head
         * of queue, if one exists, is a waiting writer.  This is
         * only a probabilistic effect since a new reader will not
         * block if there is a waiting writer behind other enabled
         * readers that have not yet drained from the queue.
         */
...