单例模式的五种写法

饿汉式

为什么叫做饿汉式,意思是很饥饿,那么就会一开始就准备好,以防以后吃不饱,名字由此而来。代码以下java

class HUNGRYMAN{
    //这里实例化方法要设置成私有的,以防外部直接new对象,破坏单例
    private HUNGRYMAN(){
    }
    //这里即为一开始就建立好对象,须要调用的时候,直接返回,不须要新建立
    private static HUNGRYMAN INSTANCE = new HUNGRYMAN();
    //调用方法返回实例对象
    public static HUNGRYMAN getInstance(){
        return INSTANCE;
    }
}
class Main{
    public static void main(String[] args) {
    //咱们在这里采用哈希值的方式来判断是否相等
        HUNGRYMAN instance1 = HUNGRYMAN.getInstance();
        HUNGRYMAN instance2 = HUNGRYMAN.getInstance();
        System.out.println("instance1的哈希值为" + instance1.hashCode() + " " + "instance2的哈希值为" + instance2.hashCode());
        System.out.println("二者是否相等:" + (instance1 == instance2));
    }
}
//输出:instance1的哈希值为366712642 instance2的哈希值为366712642
//二者是否相等:true
  • 好处:显而易见,咱们在一开始就建立好了对象,就不会涉及到线程安全的问题(线程安全是在并发建立对象的时候才会发生,可是这里咱们在类生成的时候就建立好了对象,因此不会发生线程安全的问题)。
  • 坏处:坏处也是显而易见,由于咱们在类初始化的时候就建立好了类对象,因此极可能会出现这种状况:咱们须要使用单例的时间其实不长,可是这个类存活的时间很长,假如这个时候类里面有一些别的对象,好比很长的字节数组,那么就会一直占用资源。

因为上述的状况,诞生了懒汉式:数组

懒汉式

与饿汉式相对,懒汉式只有在须要使用的时候才会建立,也就是说能够实现“延时建立”,代码以下:安全

class LAZYMAN{
    //一样,这里的实例方法也须要设置成私有的,防止其余类破坏单例
    private LAZYMAN(){
    }
    //这里定义一个LAZYMAN类型的变量instance,可是并无建立对象
    private static LAZYMAN INSTANCE;
    //在执行getInstance方法的时候才会建立对象
    public static LAZYMAN getInstance(){
        //判空时初始化,不然直接返回对象
        if(INSTANCE == null){
            INSTANCE = new LAZYMAN();
        }
        return INSTANCE;
    }
}
class Main{
    public static void main(String[] args) {
        LAZYMAN instance1 = LAZYMAN.getInstance();
        LAZYMAN instance2 = LAZYMAN.getInstance();
        System.out.println("instance1的哈希值为" + instance1.hashCode() + " " + "instance2的哈希值为" + instance2.hashCode());
        System.out.println("二者是否相等:" + (instance1 == instance2));
    }
}
//输出:instance1的哈希值为366712642 instance2的哈希值为366712642
//输出:二者是否相等:true
  • 好处:实现了延迟加载,这里不须要考虑资源消耗的问题,也就是说,只有当执行 getInstance() 方法的时候才会建立对象,避免了资源的消耗。
  • 坏处:线程不安全,由于咱们在并发的执行 getInstance() 的时候并无考虑线程线程安全的问题,这势必会致使出现问题。

由此产生了双重校验锁(DCL懒汉式)的单例模式实现方式并发

DCL懒汉式

线程安全的懒汉模式,直接来看代码:线程

class LAZYMAN{
    private LAZYMAN(){
    }
    //这里加入volatile,是为了防止实例对象建立的时候出现指令重排
    /*
    对象建立过程(非原子性):1.分配内存空间 2.执行构造方法,初始化对象 
    3.将对象指向这个内存空间。这里若是没有使用volatile修饰的话,会致使
    执行顺序不是123,多是132,致使对象建立尚未彻底建立完毕,可是外部
    执行到if(instance == null)这里的时候,已经不为空了,会直接返回,致使
    返回版初始化状态的实例,发生错误。
    */
    private static volatile LAZYMAN INSTANCE;
    //在执行getInstance方法的时候才会建立对象
    public static LAZYMAN getInstance(){
        //若是对象没有建立过,先把整个class锁住,等到第一个拿到锁的线程释放以后其余的线程在继续执行
        if(INSTANCE == null){
            synchronized(LAZYMAN.class){
                if(INSTANCE == null){
                    INSTANCE = new LAZYMAN();
                }
            }
        }
        return INSTANCE;
    }
}
class Main{
    public static void main(String[] args) {
        LAZYMAN instance1 = LAZYMAN.getInstance();
        LAZYMAN instance2 = LAZYMAN.getInstance();
        System.out.println("instance1的哈希值为" + instance1.hashCode() + " " + "instance2的哈希值为" + instance2.hashCode());
        System.out.println("二者是否相等:" + (instance1 == instance2));
    }
}
  • 好处:线程安全,看起来是十分完美的方法,又有延迟加载的功能,又线程安全
  • 坏处:咱们知道,有个东西叫作反射,能够直接获取到类对象,能够操做私有变量的值,能够直接在外部建立实例对象,因此说会破坏单例模式,也就是说,这种方法也不是绝对安全的。

关于 volatile 和 synchronized ,在另外的文章中会探讨。
这里说一下如何经过反射来破坏:code

class Main{
    public static void main(String[] args) throws Exception {
        LAZYMAN instance1 = LAZYMAN.getInstance();
        Constructor<LAZYMAN> constructor = LAZYMAN.class.getDeclaredConstructor();
        //无视私有构造器
        constructor.setAccessible(true);
        //直接建立
        LAZYMAN instance2 = constructor.newInstance();
        System.out.println("instance1的哈希值为" + instance1.hashCode() + " " + "instance2的哈希值为" + instance2.hashCode());
        System.out.println("二者是否相等:" + (instance1 == instance2));
    }
}
//输出:instance1的哈希值为366712642 instance2的哈希值为1829164700
//二者是否相等:false

借助内部类

class INNERSINGLE{
    private INNERSINGLE(){
    }
    //内部类,调用的时候才进行加载
    private static class SINGLEINNNER{
       private static INNERSINGLE instance = new INNERSINGLE();
    }
    //调用方法:getInstance()
    public static INNERSINGLE getInstance(){
      return SINGLEINNNER.instance;
    }

}
class Main{
    public static void main(String[] args) throws Exception {
        INNERSINGLE instance1 = INNERSINGLE.getInstance();
        INNERSINGLE instance2 = INNERSINGLE.getInstance();
        System.out.println("instance1的哈希值为" + instance1.hashCode() + " " + "instance2的哈希值为" + instance2.hashCode());
        System.out.println("二者是否相等:" + (instance1 == instance2));
    }
}
//输出:instance1的哈希值为366712642 instance2的哈希值为366712642
//二者是否相等:true
  • 优势:内部类与外部类不在同一时间加载,只在调用 getInstance() 方法的时候才会去初始化INSTANCE,故而节省了内存空间。能够认为是饿汉式的改进版。因此会保证线程安全,同时也作到了延迟加载。

枚举

枚举类默认是单例模式的对象

public enum ENUMSINGLE{
    INSTANCE;
    public ENUMSINGLE getInstance(){
        return INSTANCE;
    }
}
class Main{
    public static void main(String[] args) throws Exception {
        ENUMSINGLE instance1 = ENUMSINGLE.INSTANCE;
        ENUMSINGLE instance2 = ENUMSINGLE.INSTANCE;
        System.out.println("instance1的哈希值为" + instance1.hashCode() + " " + "instance2的哈希值为" + instance2.hashCode());
        System.out.println("二者是否相等:" + (instance1 == instance2));
    }
相关文章
相关标签/搜索