单例模式的五大护法

饿汉式

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

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,导致对象创建还没有完全创建完毕,但是外部
	执行到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 ,在另外的文章中会探讨。
    这里说一下如何通过反射来破坏:
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));
    }
//输出:instance1的哈希值为366712642 instance2的哈希值为366712642
//两者是否相等:true
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值