前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计模式----单例模式详解

设计模式----单例模式详解

作者头像
叔牙
发布2020-11-19 14:57:07
4950
发布2020-11-19 14:57:07
举报

单例模式是java中用的比较多的一种设计模式,目的就是让一个应用中对于某个类,只存在唯一的实例化对象。单例模式有很多实现方案,各有利弊,接下来将做详细分析介绍。

  1. 饿汉模式:利用jvm类加载机制,在类加载到jvm后就完成唯一实例的初始化 /** * 饿汉模式 * * @author Typhoon * @date 2018-04-14 21:27 Saturday */ public class SingletonPattern { /** * 类加载的时候创建唯一实例 */ private static SingletonPattern instance = new SingletonPattern(); /** * 不允许外部引用调用构造器 */ private SingletonPattern() {} /** * 返回单例 * * @return */ public static SingletonPattern getInstance() { return instance; } }

优点:利用jvm类加载完成初始化,不存在并发问题 缺点:如果一个应用很多种这种类,有些可能很少用到,都加在到内存中造 成压力

  1. 懒汉模式:也即是在jvm加载类的时候并不初始化唯一实例,而是在第一次被调用的时候被初始化 /** * 懒汉模式 * * @author Typhoon * @date 2018-04-14 21:38 Saturday */ public class Singleton2 { private static Singleton2 instance = null; private Singleton2() {} public static Singleton2 getInstance() { if(null == instance) { instance = new Singleton2(); } return instance; } }

在并发的时候,上述的懒汉模式发生了线程安全问题,实例被多次初始化

因此需要加同步控制:

再次运行测试

这次看到多线程调用已经做了同步控制 优点:用的时候才会初始化,不会过早占用内存 缺点:对方法做了同步控制,吞吐量有瓶颈

  1. 双重校验锁:其实这个是基于懒汉模式的改进,用同步代码块代替同步方法来提高性能和吞吐量 /** * 双重校验锁 * * @author Typhoon * @date 2018-04-14 21:59 Saturday */ public class SingletonPattern3 { private static SingletonPattern3 instance = null; private SingletonPattern3() {} public static SingletonPattern3 getInstance() { //并发情况下如果instance没有初始化,线程都会进synchronized块 if(null == instance) { //第一次初始化的时候并发线程在此阻塞 synchronized (SingletonPattern3.class) { //第一次初始化时候多个线程依次进入,肯定会有一个线程先初始化成功,这个时候如果新进来的线程不加判断,将会继续初始化 if(null == instance) { instance = new SingletonPattern3(); } } } return instance; } }

可以看到,双重校验锁已经解决了并发场景的唯一实例初始化问题。 优势:既实现了懒汉模式,也解决了并发初始化问题 缺点:存在jvm指令重排问题

  1. 静态内部类:通过静态内部类来实现单例模式 /** * 静态内部类 * * @author Typhoon * @date 2018-04-14 22:14 Saturday */ public class SingletonPattern4 { private static class SingletonHolder { public static SingletonPattern4 instance = new SingletonPattern4(); } private SingletonPattern4() {} public SingletonPattern4 getInstance() { return SingletonHolder.instance; } }

运行结果可以看到,静态内部类也可以实现单例模式。它和饿汉模式有点类似,都是通过jvm类加载机制创建实例,所以不存在并发问题;但是和饿汉模式不一样的地方是,只要应用中不适用内部类,jvm就不会去加载这个单例类,也不会创建单例对象,从而实现了懒汉模式中的延迟加载。 优点:不存在并发问题,延迟加载 缺点:实现麻烦,不直观

  1. 枚举

public enum SingletonPattern5 {

INSTANCE {

@Override

protected void read() {

System.out.println("read");

}

@Override

protected void write() {

System.out.println("write");

}

};

protected abstract void read();

protected abstract void write();

}

运行测试程序,看到通过枚举也实现了单例模式。使用枚举除了线程安全和防止反射调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此,《Effective Java》作者推荐使用的方法。但是,在实际工作中,很少看见有人这么写。

除了枚举的实现,其他四种实现还存在其他问题:

1)每次反序列化一个序列化的对象时都会创建一个新的实例。

2)可以使用反射强行调用私有构造器

我们使用实例来证明:

  1. 饿汉模式
  1. 懒汉模式
  1. 双重校验锁
  1. 静态内部类
  1. 枚举类

根据上述运行结果可以得知除了枚举,其他方式都存在问题。对于存在的问题如何解决呢?

1)反序列化问题:在单例中定义readResolve方法,反序列化的时候readSolve方法会被调用到,用readResolve()返回的对象直接替换掉反序列化过程中创建的对象,这样就解决了单例模式的反序列化漏洞

2)反射问题:对于反射问题,我们可以在单例类中的私有构造器中判断,如果唯一实例已经被初始化过了,直接抛异常

以饿汉模式为例:

重新运行测试:

可以看到,反序列化后和序列化之前是同一个对象,反射创建单例类对象失败。这也就解决了单例模式中的反序列化和反射漏洞。

此篇暂且分析到这里,希望能帮大家对单例模式带来更深刻的理解和认识,从而更好更安全的使用单例模式!

创作不易,请多多支持!

附带公众号:

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-04-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PersistentCoder 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档