Различие между синхронизацией поля читает и энергозависимый

В хорошей статье с некоторыми подсказками по параллелизму пример был оптимизирован к следующим строкам:

double getBalance() {
    Account acct = verify(name, password);
    synchronized(acct) { return acct.balance; }
}

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

Пример заставил меня думать немного: не был бы это быть более эффективным просто объявить acct.balance (т.е. полевой баланс класса Учетная запись) как volatile? Это должно быть более эффективным, сохранить Вас весь synchronize на доступах к acct.balance и не заблокировал бы целое acct объект. Я пропускаю что-то?

10
задан Hans-Peter Störr 23 August 2010 в 10:06
поделиться

2 ответа

Вы правы. volatile обеспечивает гарантию видимости. synchronized обеспечивает как гарантию видимости, так и сериализацию защищенных участков кода. Для ОЧЕНЬ простых ситуаций достаточно volatile, однако легко попасть в беду, используя volatile вместо синхронизации.

Если вы предположите, что Учетная запись имеет способ корректировки своего баланса, тогда волатильность не достаточно хороша

public void add(double amount)
{
   balance = balance + amount;
}

Тогда у нас возникнет проблема, если баланс изменчив без какой-либо другой синхронизации.Если бы два потока попытались вызвать add () вместе, у вас могло бы быть «пропущенное» обновление, где происходит следующее

Thread1 - Calls add(100)
Thread2 - Calls add(200)
Thread1 - Read balance (0)
Thread2 - Read balance (0)
Thread1 - Compute new balance (0+100=100)
Thread2 - Compute new balance (0+200=200)
Thread1 - Write balance = 100
Thread2 - Write balance = 200 (WRONG!)

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

Я обычно считаю, что если при написании кода я думаю: «Могу ли я использовать volatile вместо synchronized», ответом вполне может быть «да», но время / усилия, чтобы выяснить это наверняка, и опасность ошибиться - это не стоит выгоды (второстепенная производительность).

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

13
ответ дан 3 December 2019 в 22:34
поделиться

Объявление учетной записи как изменчивой связано со следующими проблемами и ограничениями

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

double getBalance () { изменчивый Аккаунт acct = verify (имя, пароль); // Неправильно .. }

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

  2. Если вам нужна синхронизация для координации изменений переменных из разных потоков, volatile не гарантирует вам атомарный доступ , потому что доступ к изменчивой переменной никогда не удерживает блокировку, он не подходит для случаев, когда мы хотим читать-обновлять-писать как атомарную операцию. Если вы не уверены, что acct = verify (имя, пароль); это одиночная атомарная операция, вы не можете гарантировать исключенные результаты

  3. Если переменная acct является ссылкой на объект, то, скорее всего, она может быть нулевой. Попытка выполнить синхронизацию для нулевого объекта вызовет исключение NullPointerException с использованием synchronized. (потому что вы эффективно синхронизируете ссылку, а не фактический объект) Где as volatile не жалуется

  4. Вместо этого вы можете объявить логическую переменную как volatile, как здесь

    private volatile boolean someAccountflag;

    public void getBalance () { Аккаунт; while (! someAccountflag) { acct = verify (имя, пароль); } }

Обратите внимание, что вы не можете объявить someAccountflag как синхронизированный, поскольку вы не можете синхронизировать примитив с помощью synchronized, synchronized работает только с объектными переменными, где как примитив или объектная переменная может быть объявлена ​​volatile

6. Статические поля final класса не обязательно должны быть изменчивыми , JVM позаботится об этой проблеме. Таким образом, someAccountflag не нужно даже объявлять изменчивым, если он является окончательным статическим или вы можете использовать ленивую инициализацию синглтона, создав учетную запись как одноэлементный объект и объявите это следующим образом: частный финальный статический AccountSingleton acc_singleton = new AccountSingleton ();

1
ответ дан 3 December 2019 в 22:34
поделиться
Другие вопросы по тегам:

Похожие вопросы: