实现单例模式,必须满足的条件:
- 单例模式的类必须为final(不允许继承)
- 私有化构造器(private)
- 建立获取实例对象的类方法(static)
//final不允许继承
pubilc final class Singleton{
//私有构造函数,不允许外部new
private Singleton(){}
//获取实例对象的类方法
public static Singleton getInstance(){
return instance;
}
}
各个实现方式优劣的判断维度:
- 线程安全(保证单例)
- 高性能(允许多线程同时获取实例对象)
- 懒加载(要使用时才去实例化)
一、饿汉式
设置一个类变量(使用static修饰),在类中使用new方法直接返回一个对象。这样做的好处就是,可以保证在类加载阶段(类初始化)时就能完成instance实例的创建。因为作为类变量,可以在类初始化执行()方法中被赋予正确的值,()方法是在编译阶段生成的,已经包含在class文件中了,包含了所有类变量的赋值动作和静态语句块的执行代码,JVM会保证()方法在多线程环境下的同步语义。
但是饿汉式只能单纯保证多线程下的唯一实例,且是在类初始化阶段就生产了类对象,而不是需要使用到的时候再生成对象,也就是说不能进行懒加载,这样的不好的地方是如果这类中的成员都是比较重的资源的话,会比较占用多的内存资源。
private static Singleton instance = new Singleton();
二、懒汉式
要做到懒汉式,也就是懒加载,就是需要用到实例的时候才进行初始化,也就是说在类加载阶段不进行初始化。可以一开始设置类变量instance为null,后续在getInstance方法中进行判断,instance是否已经被实例化,若没有则new一个对象返回。
private static Singleton instance = null;
public static Singleton getInstance(){
if(null == instance){
return new Singleton();
}
return instance;
}
这样写在多线程环境下不能保证单例,因为getInstance方法中没有做同步处理,可能会导致instance被实例化多次。
这种就其实就是没有达到单例模式的基本要求,错误的写法。
三、懒汉式+同步方法
此时需要在getInstance方法中加上synchronized关键字进行同步控制
public static synchronized Singleton getInstance(){
if(null == instance){
return new Singleton();
}
return instance;
}
但是这样做会导致后续线程获取instance时性能低下,因为同一时刻只允许一个线程访问getInstance方法。(没必要为了保证第一次创建时的线程安全问题,而放弃后续使用性能)
四、Double-Check
if(null == instance){
synchronized(Singleton.class){
if(null == instance){
return new Singleton();
}
}
}
即满足了懒加载,又满足了instance实例的唯一性,同时还可以允许多个线程同事访问getInstance方法。但是在多线程环境下,上面这段逻辑可能会导致空指针异常(且每次都要先进行判断,相当于多增加了一个判断,不过这种应该可以忽略不计),因为如果Singleton构造器中还需要初始化其他资源,由于JVM运行时指令重排序和Happens-Before规则,其他资源和instance实例顺序没有前后约束,则会出现instance已经实例化完成,而其他资源并没有完成实例化,另外一个线程访问进来后,判断instance不为null,直接使用类的实例变量资源时,会报空指针异常。
五、Volatile+Double-Check
public volatile static Singleton instance = null;
通过加上volatile关键字,直接禁止JVM的指令重排序。
六、Holder
通过在类中建立静态内部类的方式持有实例对象。
private static class Holder{
private static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return Holder.instance;
}
把new Singleton()放在静态内部类中,在首次使用Holder类时,类加载初始化阶段执行的()方法,保证了同步语义(保证内存可见性,JVM指令的顺序性和原子性)。这种方式是最好的单例设计之一。
七、Enum
枚举类型不允许被继承,同样是线程安全的且只能被实例化一次。
public enum Singleton{
INSTANCE;
Singleton(){}
public static void method(){
}
public static Singleton getInstance(){
return INSTANCE;
}
}
上面的代码不能实现懒加载,可以按照类似于Holder的方式进行改进:
public class Singleton{
private Singleton(){}
private enum EnumHolder{
INSTANCE;
private Singleton instance;
EnumHolder(){
this.instance = new Singleton();
}
private Singleton getSingleton(){
return instance;
}
}
public static Singleton getInstance(){
return EnumHolder.INSTANCE.getSingleton();
}
}
本文是出自《Java高并发编程详解》,记录下来供大家学习~