Параллельное чтение и запись Java: блокировка чтения и блокировка + изменчивость

Предположим, что читатели = 10 х писатели, какое из следующих решений лучше с точки зрения пропускной способности. Какое решение лучше использовать в производственном коде?

  1. Использование одиночной блокировки для операции установки и изменчивой переменной для операции получения.
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public final class InstanceProvider {

    private final Lock wLock = new ReentrantLock();
    private volatile List<Instance> instances;

    public void setInstances(List<Instance> newInstances) {
        wLock.lock();
        try {
            doSmthWith(newInstances);
            instances = newInstances;
        } finally {
            wLock.unlock();
        }
    }

    public List<Instance> getInstances() {
        return instances;
    }

    @Getter
    public static final class Instance {

        private final String name;

        public Instance(String name) {
            this.name = name;
        }

    }
}
  1. Использование единой блокировки чтения-записи для операций установки и получения.
import java.util.List;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public final class InstanceProvider {

    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private List<Instance> instances;

    public void setInstances(List<Instance> newInstances) {
        lock.writeLock().lock();
        try {
            doSmthWith(newInstances);
            instances = newInstances;
        } finally {
            lock.writeLock().unlock();
        }
    }

    public List<Instance> getInstances() {
        lock.readLock().lock();
        try {
            return instances;
        } finally {
            lock.readLock().unlock();
        }
    }

    @Getter
    public static final class Instance {

        private final String name;

        public Instance(String name) {
            this.name = name;
        }

    }
}

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

Блокировка ничего не дает; volatile — это все, что вам здесь нужно.

shmosel 04.06.2024 22:35

Что делает doSmthWith()? Изменяет ли это список? Зачем нужна синхронизация?

shmosel 04.06.2024 22:55

В этом коде есть фундаментальная проблема: ничто не гарантирует неизменяемость списка. У вызывающего абонента setInstances есть ссылка на список, как и у каждого вызывающего абонента getInstances(). Каждый мог изменить список и, следовательно, все сломать. Называть этот класс также ошибочно InstanceProvider; он не предоставляет экземпляры, он просто поддерживает список экземпляров, предоставленный кем-то другим.

Holger 12.06.2024 12:11
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
3
72
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Блокировка записи также не требуется. Если это буквально так и вас не волнует порядок, просто создайте это поле volatile и вообще не используйте никаких блокировок. Это работает, потому что Java гарантирует, что присвоение ссылок является атомарным.

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

Вот почему здесь вы «уходите» вообще без блокировки. Вы просто хотите убедиться, что событие «прежде чем» не отображается и не означает, что один поток может писать в это поле, а другим потокам требуется день, чтобы «увидеть» это обновление, что JMM позволяет JVM сделать, если вы также удалите volatile. Так что держите это при себе. Блокировка все равно может произойти — дело в том, что решает JVM, а не вы. volatile гарантирует, что запись в это поле и чтение в это поле будут установлены до того, как вы «увидите» это.

Смысл блокировки чтения/записи заключается в том, что несколько объектов могут читать одновременно, не блокируя друг друга, но блокировка записи блокирует все остальные записи и все другие чтения (и блокируется любым открытым чтением). Здесь вам ничего этого не нужно.

Ответ принят как подходящий

Блокировка полезна в нескольких случаях:

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

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

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

В вашем сценарии ни одна из этих проблем не имеет значения. Операция записи (setInstances()) обновляет только ссылочное поле, которое по своей сути является атомарной операцией. Видимость и безопасная публикация обеспечиваются за счет создания поля volatile, которое гарантирует то, что записи сразу и полностью видны при последующих чтениях.

Так что запирать вообще не надо. Просто ставьте volatile.

Другие вопросы по теме