Синхронизираната ключова дума на Java изчиства ли кеша?

Само Java 5 и по-нова версия. Да приемем многопроцесорен компютър със споделена памет (вероятно използвате такъв в момента).

Ето код за ленива инициализация на сингълтън:

public final class MySingleton {
  private static MySingleton instance = null;
  private MySingleton() { } 
  public static MySingleton getInstance() {
    if (instance == null) {
      synchronized (MySingleton.class) {
        if (instance == null) {
          instance = new MySingleton();
        }
      }
    }
    return instance;
  }
}

Трябва ли instance да се декларира volatile, за да попречи на оптимизатора да пренапише getInstance(), както следва (което би било правилно в последователна програма):

public static MySingleton getInstance() {
  if (instance == null) {
    synchronized (MySingleton.class) {
      // instance must be null or we wouldn't be here  (WRONG!)
      instance = new MySingleton();
    }
  }
}

Ако приемем, че оптимизаторът не пренаписва кода, ако instance не е деклариран, volatile все още ли е гарантирано, че ще бъде изчистен в паметта, когато се излезе от блока synchronized, и ще се прочете от паметта, когато се влезе в блока synchronized?

РЕДАКТИРАНЕ: Забравих да направя getInstance() статичен. Не мисля, че това променя валидността на отговорите; всички знаехте какво имах предвид.


person Mark Lutton    schedule 30.08.2010    source източник
comment
Съвет: можете да използвате javap -c com.example.ClassName, за да разглобите кода, така че да можете да проверите как е бил компилиран и дали компилаторът е променил/оптимизирал едното или другото. Проверих го за вас с този на JDK 1.6 и компилаторът не премахна вложеното if.   -  person BalusC    schedule 30.08.2010
comment
Използването на javap е интересно, но оптимизаторът в javac обикновено е доста слаб. Нарочно. Истинският оптимизатор е в JIT компилатора по време на изпълнение.   -  person Darron    schedule 30.08.2010
comment
Извинявам се, че маркирах само един от отговорите като Отговор. Всички до момента заслужават да бъдат проверени, но ми позволява да проверя само един.   -  person Mark Lutton    schedule 30.08.2010
comment
volatile не е необходимо. познанията на обществото за двойно проверено заключване са остарели.   -  person irreputable    schedule 31.08.2010
comment
неуважаван, можеш ли да обясниш?   -  person Mark Lutton    schedule 31.08.2010


Отговори (5)


Да, instance трябва да се декларира volatile. Дори тогава се препоръчва да не използвате двойно проверено заключване. Той (или за да бъдем точни, моделът на паметта на Java) имаше сериозен недостатък, който позволяваше публикуването на частично внедрени обекти. Това е поправено в Java5, все още DCL е остарял идиом и няма нужда да го използвате повече - използвайте идиома на притежателя на мързелива инициализация вместо това.

От Java Concurrency на практика, раздел 16.2:

Истинският проблем с DCL е предположението, че най-лошото нещо, което може да се случи при четене на препратка към споделен обект без синхронизация, е погрешно да видите остаряла стойност (в този случай null); в този случай идиомата на DCL компенсира този риск, като опита отново със задържано заключване. Но най-лошият случай всъщност е значително по-лош - възможно е да видите текуща стойност на референтните, но остарели стойности за състоянието на обекта, което означава, че обектът може да се види като в невалидно или неправилно състояние.

Последващите промени в JMM (Java 5.0 и по-нови) позволиха на DCL да работи ако resource е направено volatile и въздействието върху производителността от това е малко, тъй като volatile четенията обикновено са само малко по-скъпи от енергонезависимите четения. Това обаче е идиом, чиято полезност до голяма степен е преминала - силите, които са го мотивирали (бавна синхронизация без конкуренция, бавно стартиране на JVM), вече не са в действие, което го прави по-малко ефективен като оптимизация. Идиомът на мързеливия притежател на инициализация предлага същите предимства и е по-лесен за разбиране.

person Péter Török    schedule 30.08.2010
comment
+1 Много интересно. Stackoverflower винаги ме надхитрява. Това е безкрайно упражнение за смирение. - person gawi; 30.08.2010
comment
Ако Edsger Dijkstra все още беше с нас, той вероятно щеше да отбележи, че ако експертните програмисти винаги са надхитрени от stackoverflow, професията е в лошо състояние. - person Mark Lutton; 30.08.2010
comment
идиомата на класа на притежателя работи само за сингълтони. не е общ механизъм на мързелива инициализация. - person irreputable; 31.08.2010
comment
@irrep, вярно, но този въпрос е за singletons, а не за мързеливо init като цяло. - person Péter Török; 31.08.2010

Да, екземплярът трябва да бъде непостоянен, използвайки двойно проверено заключване в Java, защото в противен случай инициализирането на MySingleton може да изложи частично конструиран обект на останалата част от системата. Също така е вярно, че нишките ще се синхронизират, когато достигнат оператора „синхронизирано“, но в този случай това е твърде късно.

Wikipedia и няколко други въпроса за Stack Overflow имат добри дискусии за „заключване с двойна проверка“ , така че бих ви посъветвал да прочетете за това. Също така бих посъветвал да не го използвате, освен ако профилирането не покаже реална нужда от производителност в този конкретен код.

person samkass    schedule 30.08.2010
comment
(от статия в javaworld) Често предлагано непоправяне е полето за ресурс на SomeClass да се декларира като непостоянно. Въпреки това, докато JMM предотвратява пренареждането на записите в променливи променливи по отношение една спрямо друга и гарантира, че те се изхвърлят незабавно в основната памет, той все пак позволява четенията и записите на променливи променливи да бъдат пренаредени по отношение на енергонезависимите четения и записи. Това означава - освен ако всички полета за ресурси също не са непостоянни - нишката B все още може да възприема ефекта на конструктора като случващ се, след като ресурсът е настроен да препраща към новосъздадения ресурс. - person gawi; 30.08.2010
comment
Това беше коригирано в Java5, въпреки че си струва да се отбележи, че в по-старите виртуални машини дори volatile няма да реши проблема. - person samkass; 30.08.2010

Има по-безопасен и по-четлив идиом за мързелива инициализация на Java singletons:

class Singleton {

  private static class Holder {
    static final Singleton instance = create();
  }

  static Singleton getInstance() { 
    return Holder.instance; 
  }

  private Singleton create() {
    ⋮
  }

  private Singleton() { }

}

Ако използвате по-подробния шаблон за заключване с двойна проверка, трябва да декларирате полето volatile, както други вече отбелязаха.

person erickson    schedule 30.08.2010

Вижте http://www.javaworld.com/jw-02-2001/jw-0209-double.html

person gawi    schedule 30.08.2010

Не. Не е необходимо да е непостоянен. Вижте Как да решите декларацията Double-Checked Locking is Broken в Java?

всички предишни опити са неуспешни, защото ако можете да измамите java и да избегнете volatile/sync, java може да ви измами и да ви даде неправилен изглед на обекта. но новата final семантика решава проблема. ако получите препратка към обект (чрез обикновено четене), можете безопасно да прочетете неговите final полета.

person irreputable    schedule 30.08.2010
comment
И все пак идиомата на притежателя на мързелива инициализация постига същото, по по-ясен начин, с по-малко код (по този начин по-малък шанс за грешки) и без никаква нужда от изрична синхронизация. - person Péter Török; 31.08.2010
comment

Реших го. Така че, за да го накарам да работи след импортиране на модела от базата данни на sqlite, направих следното:

a) изтри импортираната колона „ProcessNameId“.

б) след това добави асоциацията между двата обекта - той автоматично използва "ProcessNameId" като FK id, което беше добро, тъй като съвпадаше с това, което вече имах в базата данни (в противен случай трябваше да преследвам това)

Нещата заработиха добре след това.

- person irreputable; 31.08.2010
comment
Този въпрос се отнася изрично за сингълтоните. - person erickson; 31.08.2010