单例模式总结
饿汉式单例
//优点:没有任何锁、执行效率较高,在用户体验上来看比懒汉式好,绝对的线程安全,在线程还没出现之前就实例
//化了,不可能存在安全问题
//缺点:在类加载的时候就初始化了,不管你用还是不用,浪费了资源,占用内存
public class HungrySingleton {
//类初始化的顺序
//先静态、后动态
//先属性、后方法
//先上后下
private static final HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
饿汉式静态块单例
//只不过是把静态单例的初始化放到了静态初始化块中
public class HungryStaticSingleton {
private static final HungryStaticSingleton hungrySingleton;
static {
hungrySingleton = new HungryStaticSingleton();
}
private HungryStaticSingleton(){}
public static HungryStaticSingleton getInstance(){
return hungrySingleton;
}
}
懒汉式单例(不推荐)
//在外部需要使用的时候才进行实例化
public class LazySimpleSingleton {
private LazySimpleSingleton(){}
//静态块,公共内存区域
private static LazySimpleSingleton lazy = null;
//不加synchronized的时候会出现线程不安全的问题
//对方法进行同步,保证线程安全,但是效率比较低,每个线程在获取单例的时候都要进行同步
public synchronized static LazySimpleSingleton getInstance(){
if(lazy == null){
lazy = new LazySimpleSingleton();
}
return lazy;
}
}
双重检查(推荐)
//线程安全,延迟加载,效率较高
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton lazy = null;
private LazyDoubleCheckSingleton(){}
public static LazyDoubleCheckSingleton getInstance(){
//这个地方对同步进行了双重的检验,如果发现不为空, 则直接不去竞争锁了, 提高了性能
if(lazy == null){
synchronized (LazyDoubleCheckSingleton.class){
if(lazy == null){
lazy = new LazyDoubleCheckSingleton();
//这个地方的Java代码虽然就一句, 但是JVM在执行的时候会有多条指令, 为了优化效 //率, JVM可能会进行指令重排,
//1.分配内存给这个对象
//2.初始化对象
//3.设置lazy指向刚分配的内存地址
//4.初次访问对象
//如何解决内存重排的问题呢? 把 lazy 用volatile 关键字进行修饰就可以了。
//在以后的课程中会介绍volatile 的内存语义。
}
}
}
return lazy;
}
}
静态内部类
//属于懒汉式单例
//此种形式兼顾了饿汉式的内存浪费,也兼顾了synchronized性能问题
//优点:避免了线程不安全的问题,延迟加载,效率高
public class LazyInnerClassSingleton {
private LazyInnerClassSingleton (){
if(LazyHolder.LAZY != null){
throw new RuntimeException("不允许创建多个实例");
}
}
//static是为了使单例的空间共享,final是为了方法不会被重写和重载
public static final LazyInnerClassSingleton getInstance(){
//在返回结果前,一定会先加载内部类
return LazyHolder.LAZY;
}
//这种写法是利用了类加载机制
//首先,java的内部类是一种编译行为,所有的类在jvm中都是平等的,加载和初始化过程都是一样的,无论是内部类还是普通类
//其次,类何时被初始化的呢?当我们首次访问一个类的静态成员的时候,这个类才会被初始化1)在类的实例存在之前, 构造器就可以调用, 而且我们调用构造器的时候并不是使用对象进行调用的, 那很明显就是用类调用的
// 其次, 我们在类的静态方法中也可以调用类的构造器这一点就非常清晰地证明了类的构造器是类的静态成员。
//明白了这些之后, 这种写法的整个初始化过程就非常清晰了, 首先是类进行了加载, 但是这时并没有进行初始化, 当我调用getInstance()方法的时候,就会访问静态内部类的静态域 LAZY,
//这时候, 静态内部类才进行初始化, 所以我们利用类加载的特性实现了示例的懒加载。
//而且, 使用这种方法只初始化一次的目的是由JVM的类加载机制实现的, 更加高效, 更加优雅
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton ();
}
}