Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
public class Something {
private Something() {}
private static class LazyHolder {
private static final Something INSTANCE = new Something();
}
public static Something getInstance() {
return LazyHolder.INSTANCE;
}
}
private static class LazyHolder {
private static final Something INSTANCE;
private static final Exception EXCEPTION;
static {
try {
INSTANCE = new Something();
EXCEPTION = null;
} catch ( Exception ex ) {
INSTANCE = null; EXCEPTION = ex;
}
}
}
public static Something getInstance() throws Exception {
if ( LazyHolder.INSTANCE != null )
return LazyHolder.INSTANCE
else throw EXCEPTION;
}
public class SingletonHolder {
private SingletonHolder() {
throw new RuntimeException("test exception");
}
private static class LazyHolder {
private static final SingletonHolder INSTANCE = new SingletonHolder();
}
public static SingletonHolder getInstance() {
return LazyHolder.INSTANCE;
}
public static void main(String[] args) {
SingletonHolder instance = SingletonHolder.getInstance();
System.out.println(instance);
}
}
Exception in thread "main" java.lang.ExceptionInInitializerError at org.relgames.test.SingletonHolder.getInstance(SingletonHolder.java:16) at org.relgames.test.SingletonHolder.main(SingletonHolder.java:20) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134) Caused by: java.lang.RuntimeException: test exception at org.relgames.test.SingletonHolder.<init>(SingletonHolder.java:8) at org.relgames.test.SingletonHolder.<init>(SingletonHolder.java:6) at org.relgames.test.SingletonHolder$LazyHolder.<clinit>(SingletonHolder.java:12) ... 7 more
Для остальных (если они не используют барьеры) никаких гарантий нет.
Единственный способ прочитать там что-то не то — это позвать get на неинициализированном экземпляре DummyInstance.
конструктор еще тупо не успел выполниться
Поле instance для данного конкретного экземпляра записывается только один раз (причем под synchronized), так что я не вижу тут проблем.
public final class Singleton {
private int answer = 42;
...
}
// thread 1:
s1 = Singleton.getInstance(); // creates a new intance
assert s1.answer == 42; // works fine: sequential consistency
// thread 2:
s2 = Singleton.getInstance(); // returns an instance created by thread 1
assert s2.answer == 42; // may fail: s2.answer can be 0 (the default for int)
Java Concurrency in Practice mentions this in section 16.3:
Initialization safety guarantees that for properly constructed objects, all threads will see the correct values of final fields that were set by the constructor, regardless of how the object is published. Further, any variables that can be reached through a final field of a properly constructed object (such as the elements of a final array or the contents of a HashMap referenced by a final field) are also guaranteed to be visible to other threads. For objects with final fields, initialization safety prohibits reordering any part of construction with the initial load of a reference to that object. All writes to final fields made by the constructor, as well as to any variables reachable through those fields, become “frozen” when the constructor completes, and any thread that obtains a reference to that object is guaranteed to see a value that is at least as up to date as the frozen value. Writes that initialize variables reachable through final fields are not reordered with operations following the post-construction freeze.
Проблема в том, что второму потоку пофиг на этот synchronized, он до него не доходит, потому что идет в обход через DummyInstance, минуя все барьеры, необходимые для чтения консистентного содержимого целевого экземпляра. Барьеры должны идти парами, если в одном потоке есть барьер на запись, а в другом нет барьера на чтение, то смысла в первом барьере нет.
Memory Barriers
Plus the special final-field rule requiring a StoreStore barrier in
x.finalField = v; StoreStore; sharedRef = x;
Data Dependency and Barriers
The need for LoadLoad and LoadStore barriers on some processors interacts with their ordering guarantees for dependent instructions. On some (most) processors, a load or store that is dependent on the value of a previous load are ordered by the processor without need for an explicit barrier. This commonly arises in two kinds of cases, indirection:
Load x; Load x.field
and control
Load x; if (predicate(x)) Load or Store y;
Processors that do NOT respect indirection ordering in particular require barriers for final field access for references initially obtained through shared references:
x = sharedRef; ... ; LoadLoad; i = x.finalField;
Conversely, as discussed below, processors that DO respect data dependencies provide several opportunities to optimize away LoadLoad and LoadStore barrier instructions that would otherwise need to be issued. (However, dependency does NOT automatically remove the need for StoreLoad barriers on any processor.)
— Громоздкий код
— 3 дополнительных класса: интерфейс и 2 реализации
Function Pointer — забытая реализация шаблона Singleton