Исключение исключения и синхронизация - PullRequest
3 голосов
/ 07 января 2011

У меня есть метод, к которому многие потоки обращаются параллельно, который использует класс с двумя синхронизированными методами, которые я не контролирую.getObject и createNewObject.Я хочу быть уверен, что я не создаю несколько объектов (MyObject).

MyObject obj;
public void method1() {
   obj = getObject("key");
   if (obj == null)
      obj = createNewObject("key");
  }

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

Какой из следующих методов будет предпочтительным?Производительность, безопасность и дизайн мудрый.Я слышал, что тип двойной блокировки (метод 3) не работает?Может быть, я должен просто использовать method1?

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

MyObject obj;
public synchronized void method1() {
   obj = getObject("key");
   if (obj == null)
      obj = createNewObject("key");
  }

public void method2() {
   obj = getObject("key");
   if (obj == null)
       try {
          obj = createNewObject("key");
       } catch (Exception e) { // ops, someone already created object "key"
            obj = getObject();
       }
  }

public void method3() {
   obj = getObject("key");
   if (obj == null)
       obj = getObj("key");
}
public synchronized MyObject getObj(String key) {
    MyObject obj = getObject(key);
    if (obj == null)
        obj = createNewObject(key);
    return obj;
 }

Ответы [ 6 ]

8 голосов
/ 07 января 2011

Начните с использования method1, пока профилировщик не скажет вам, что это бутылочное горлышко.Это самая чистая реализация, и вы знаете, что она будет работать правильно все время.Если позже вы увидите данные, которые показывают, что вы тратите много времени на последовательные вызовы, то вы можете подумать о том, чтобы попробовать что-то еще.

4 голосов
/ 07 января 2011

Это требует некоторого тестирования и профилирования, но я вполне уверен, что вы не добьетесь значительной производительности, используя какие-либо приемы, потому что синхронизация будет выполняться в любом случае, так как вы каждый раз вызываете метод getObject (), чтосинхронизированы.Так что это не различие типа «синхронизация / отсутствие синхронизации», а скорее «синхронизация / двойная синхронизация», которая не должна быть такой большой.Если вы все равно синхронизируете, лучше сделать это в полной мере.Что в вашем случае означает method1 ().

UPDATE

Хотя method2 () тоже может показаться многообещающим, я только что понял проблему с ним: так как он не '• синхронизировать запись в поле obj, другие потоки могут не увидеть его обновленное значение.Так что если к полям obj обращаются другие потоки, кроме потока, который вызывает method2 (), то method2 () не верен.

Если вы сделаете поле obj энергозависимым, я думаю, что этоработать (хотя не уверен на 100%), так как getObject () синхронизирован, поэтому не должно быть проблемы "энергозависимая ссылка на энергонезависимый объект".После возврата getObject () он выполняет барьер записи, поэтому гарантируется, что в основной памяти будет полностью инициализированный объект.И поскольку ни один поток не имеет локально кэшированной копии этого объекта, любой поток может получить доступ к полю obj.Если объект, на который ссылается поле obj, не является изменяемым, в этом случае весь доступ к нему должен быть синхронизирован в любом случае.

Хотя это все еще не имеет особого смысла.Полностью несинхронизированный доступ для чтения по-прежнему невозможен, поэтому чистая реализация все же лучше, чем «умная».

3 голосов
/ 07 января 2011

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

0 голосов
/ 08 января 2011

Прочитав немного, я считаю, что действительно лучшим решением будет использование идиомы класса держателя Initialize-On-Demand:

private static class LazySomethingHolder {
    public static Something something = new Something();
}

public static Something getInstance() {
    return LazySomethingHolder.something;
}

У него нет проблем с параллелизмом и нет блокировки даже для изменчивых переменных на общем пути.

0 голосов
/ 07 января 2011

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

0 голосов
/ 07 января 2011

РЕДАКТИРОВАТЬ: Ниже я написал идею двойной проверки блокировки, но у него есть некоторые существенные недостатки, которые описаны в

---- Оригинальный ответ ниже ----

Лучшее решение это:

Object obj;

public Object getObject() {
    if( obj == null ) {
        synchronized(this) { // or on something else
            if( obj == null ) {
                obj = createObject();
            }
        }
    }
    return obj;  
}   

private Object createObject() {
    ...
} 

Преимущество состоит в том, что синхронизация происходит только в критической фазе создания объекта, но все же работает.

...