发布对象:使对象可以在当前作用域以外的代码中使用。比如将一个对象的引用保存到其他代码可以访问的地方,或者在某一个非私有的方法中返回该引用,或者将引用传递到其他类的方法中
逸出:当某个不应该发布的对象被发布时,就称为逸出
1.懒汉式(未加锁)
public class UnsafeLazyInitialization { private static Resource resource; public static Resource getInstance() { if (resource == nu11) resource = new Resource(); // 不安全的发布 return resource; } } 当线程A调用getInstance后,线程B随后调用getInstance,但是因为没有同步线程B可能会读取到一个失效的值或者A刚判断完resource==null还未执行赋值时到B紧接进行判断那么A、B都会进行赋值,就会产生错误2.懒汉式(双重检查加锁)
public class DoubleCheckedLocking { private static Resource resource; private DoubleCheckedLocking(){ } public static Resource getInstance() { if (resource == null) { synchronized (DoubleCheckedLocking.class) { if (resource == null) { resource = new Resource(); } } } return resource; } } 当线程A调用getInstance后,在执行resource = new Resource();是可能会重排导致线程B可能会读取到一个失效的值就会产生错误1.懒汉式(加锁)
public class UnsafeLazyInitialization { private static Resource resource; public static synchronized Resource getInstance() { if (resource == nu11) resource = new Resource(); return resource; } } synchronized可以使所有线程以串行方式执行getInstance,并保证可见性2.懒汉式(双重检查加锁+volatile)
public class DoubleCheckedLocking { private static volatile Resource resource; private DoubleCheckedLocking(){ } public static Resource getInstance() { if (resource == null) { synchronized (DoubleCheckedLocking.class) { if (resource == null) { resource = new Resource(); } } } } } volatile可以防止重排序3.饿汉式
public class EagerInitialization { // 私有构造函数 private EagerInitialization() { } private static Resource resource = new Resource() ; public static Resource getResource(){ return resource;} } 单例实例是在类装载的时候进行创建,只会被执行一次,所以它是线程安全的。缺陷:如果构造函数中有着大量的事情操作要做,那么类的装载时间会很长,影响性能。如果只是做的类的构造,却没有引用,那么会造成资源浪费适用场景为:(1)私有构造函数在实现的时候没有太多的处理(2)这个类在实例化后肯定会被使用4.枚举式
public class SingletonExample { private SingletonExample() { } public static SingletonExample getInstance() { return Singleton.INSTANCE.getInstance(); } private enum Singleton { INSTANCE; private SingletonExample singleton; Singleton() { singleton = new SingletonExample(); } public SingletonExample getInstance() { return singleton; } } } 由于枚举类的特殊性,枚举类的构造函数Singleton方法只会被实例化一次,且是这个类被调用之前。这个是JVM保证的。对比懒汉与饿汉模式,它的优势很明显。5.占位类模式
public class ResourceFactory{ private static class ResourceHolder{ public static Resource resource = new Resource () ; } public static Resource getResource () { return ResourceHolder.resource; } }在初始器中采用了特殊的方式来处理静态域(或者在静态初始化代码块中初始化的值),并提供了额外的线程安全性保证。静态初始化器是由JVM在类的初始化阶段执行,即在类被加载后并且被线程使用之前。由于JVM将在初始化期间获得一个锁,并且每个线程都至少获取一次这个锁以确保这个类已经加载,因此在静态初始化期间, 内存写入操作将自动对所有线程可见。因此无论是在被构造期间还是被引用时,静态初始化的对象都不需要显式的同步。然而,这个规则仅适用于在构造时的状态,如果对象是可变的,那么在读线程和写线程之间仍然需要通过同步来确保随后的修改操作是可见的,以及避免数据破坏。
JVM将推迟ResourceHolder的初始化操作,直到开始使用这个类时才初始化,并且由于通过一个静态初始化来初始化Resource,因此不需要额外的同步。当任何一个线程第一次调用getResource时,都会使ResourceHolder被加载和被初始化,此时静态初始化器将执行Resource的初始化操作。
