代码来自与 SonarQube
其它外部的文档 The “Double-Checked Locking is Broken” Declaration
描述
Double-checked locking is the practice of checking a lazy-initialized object’s state both before and after a synchronized block is entered to determine whether or not to initialize the object.
It does not work reliably in a platform-independent manner without additional synchronization for mutable instances of anything other than float or int. Using double-checked locking for the lazy initialization of any other type of primitive or mutable object risks a second thread using an uninitialized or partially initialized member while the first thread is still creating it, and crashing the program.
There are multiple ways to fix this. The simplest one is to simply not use double checked locking at all, and synchronize the whole method instead. With early versions of the JVM, synchronizing the whole method was generally advised against for performance reasons. But synchronized performance has improved a lot in newer JVMs, so this is now a preferred solution. If you prefer to avoid using synchronized altogether, you can use an inner static class to hold the reference instead. Inner static classes are guaranteed to load lazily.
翻译:
双重检查锁定是在进入同步块之前和之后检查延迟初始化对象的状态以确定是否初始化对象的一种实践。
如果不对float或int以外的任何可变实例进行额外同步,则无法以独立于平台的方式可靠地工作。如果对任何其他类型的原始对象或可变对象进行延迟初始化使用双重检查锁定,则在第一个线程仍在创建第二个线程时,该线程将使用未初始化或部分初始化的成员,这会导致程序崩溃。
有多种解决方法。最简单的方法是根本不使用双重检查锁定,而是同步整个方法。对于早期版本的JVM,出于性能原因,通常建议不要同步整个方法。但是,在新的JVM中,同步性能已大大提高,因此,现在这是首选的解决方案。如果您希望完全避免使用同步,则可以使用内部静态类来保存引用。内部静态类保证延迟加载。
不规范的代码示例
@NotThreadSafe
public class DoubleCheckedLocking {
private static Resource resource;
public static Resource getInstance() {
if (resource == null) {
synchronized (DoubleCheckedLocking.class) {
if (resource == null)
resource = new Resource();
}
}
return resource;
}
static class Resource {
}
}
规范的代码
1.整个方法同步
@ThreadSafe
public class SafeLazyInitialization {
private static Resource resource;
public synchronized static Resource getInstance() {
if (resource == null)
resource = new Resource();
return resource;
}
static class Resource {
}
}
2.延迟初始化
@ThreadSafe
public class ResourceFactory {
private static class ResourceHolder {
// This will be lazily initialised
public static Resource resource = new Resource();
}
public static Resource getResource() {
return ResourceFactory.ResourceHolder.resource;
}
static class Resource {
}
}
3.使用 volatile 关键字
class ResourceFactory {
private volatile Resource resource;
public Resource getResource() {
Resource localResource = resource;
if (localResource == null) {
synchronized (this) {
localResource = resource;
if (localResource == null) {
resource = localResource = new Resource();
}
}
}
return localResource;
}
static class Resource {
}
}
jdk 13 synchronized 的优化
synchronized 在较新的JVM中,性能已大大提高,因此现在是首选解决方案。
下面摘自 java 13 版本中的 CopyOnWriteArrayList 的代码。
public boolean add(E e) {
synchronized (lock) {
Object[] es = getArray();
int len = es.length;
es = Arrays.copyOf(es, len + 1);
es[len] = e;
setArray(es);
return true;
}
}
public E set(int index, E element) {
synchronized (lock) {
Object[] es = getArray();
E oldValue = elementAt(es, index);
if (oldValue != element) {
es = es.clone();
es[index] = element;
}
// Ensure volatile write semantics even when oldvalue == element
setArray(es);
return oldValue;
}
}
public E remove(int index) {
synchronized (lock) {
Object[] es = getArray();
int len = es.length;
E oldValue = elementAt(es, index);
int numMoved = len - index - 1;
Object[] newElements;
if (numMoved == 0)
newElements = Arrays.copyOf(es, len - 1);
else {
newElements = new Object[len - 1];
System.arraycopy(es, 0, newElements, 0, index);
System.arraycopy(es, index + 1, newElements, index,
numMoved);
}
setArray(newElements);
return oldValue;
}
}