Должны ли геттеры/сеттеры примитивных типов блокироваться с помощью ReadWriteLock в многопоточном приложении?

StackOverflow https://stackoverflow.com/questions/5014266

Вопрос

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

class Example {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private int i;
    private boolean b;

    public int getI() {
      lock.readLock().lock();
      final int tmp = i;
      lock.readLock().unlock(),
      return tmp;
    }

    public void setI( final int i ) {
      lock.writeLock().lock();
      this.i = i;
      lock.writeLock().unlock();
    }

    public boolean getB() {
      lock.readLock().lock();
      final boolean tmp = b;
      lock.readLock().unlock(),
      return tmp;
    }

    public void setB( final boolean b ) {
      lock.writeLock().lock();
      this.b = b;
      lock.writeLock().unlock();
    }
}

Для простоты я исключил try...finally блоки вокруг замков в этом примере.

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

Что, если бы примитивы были double или long?

Это было полезно?

Решение

Это зависит.

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

Example e = ...;

synchronized (e) {
    e.setI(e.getI() + 10);
}

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

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

у вас есть что-то вроде AtomicInteger в Java, которое хорошо работает с многопоточным приложением.

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

Пример неизменяемого класса:

final class Example {
    private final int i;
    private final boolean b;

    public Example(int i, boolean b){
        this.i = i ;
        this.b = b;
    }

    public int getI() {
        return i;
    }

    public boolean getB() {
        return b;
    }
}

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

напримерПредположим, у вас есть 32-ядерная система, которая прекрасно масштабируется и работает в 32 раза быстрее, чем на 1-ядерной системе.Однако доступ к полю без блокировки занимает 1 нс, с блокировкой — 1 мкс (1000 нс), поэтому в конечном итоге ваше приложение может работать примерно в 30 раз медленнее.(на 1000 медленнее / на 32 быстрее). Если у вас всего 4 ядра, оно может быть в сотни раз медленнее, что в первую очередь противоречит цели использования многопоточности.ИМХО.

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

Как упоминалось в одном из предыдущих сообщений, вам необходимо знать о последовательностях чтения и обновления, что-то вроде инкремента I(int num), который, скорее всего, вызовет getI() и setI() - в этом случае вы можете добавить «синхронизированный инкрементI(int num)' в ваш класс примера.Затем блокировка выполняется на более высоком уровне, что снижает необходимость в отдельных блокировках чтения и записи и является объектно-ориентированным, поскольку данные и поведение остаются вместе.Этот метод еще более полезен, если вы читаете/обновляете несколько полей одновременно.

Хотя если вы просто читаете/записываете/обновляете одно поле за раз, то классы AtomicXX более подходят.

Не следует использовать блокировку для примитивных типов, String (они неизменяемы) и потокобезопасных типов (например, коллекций из «параллельного» пакета).

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top