Нужно ли блокировать методы получения / установки простых типов с помощью ReadWriteLock в многопоточном приложении? - PullRequest
2 голосов
/ 16 февраля 2011

У меня есть класс 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?

Ответы [ 6 ]

4 голосов
/ 16 февраля 2011

Это зависит.

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

Example e = ...;

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

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

3 голосов
/ 16 февраля 2011

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

2 голосов
/ 17 февраля 2011

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

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

Хотя если вы просто читаете / пишете / обновляете по одному полю за раз, то классы AtomicXX больше подходят

2 голосов
/ 16 февраля 2011

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

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

2 голосов
/ 16 февраля 2011

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

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

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;
    }
}
0 голосов
/ 16 февраля 2011

Не следует использовать блокировку для примитивных типов, String (они являются неизменяемыми) и поточно-ориентированных типов (например, коллекции из пакета "concurrent").

...