设计模式--单例模式的实现方式

fightingting 2019-09-28 原文

设计模式–单例模式的实现方式

90%程序员第一个听过的设计模式一定是单例模式,我觉得是因为其实现简单,理解起来容易,才如此流行。正因为它的知名度和流行度,单例模式的实现方式有很多,而且一直在更新。我们今天就讨论下目前主流生成单例模式的方式。

 我们听过最多的单例模式实现方式是饿汉式,懒汉式,但其实还有静态内部类等其他方式。

1.饿汉式

public class Singleton {

    private static Singleton singleton = new Singleton();

    private Singleton(){}

    public static Singleton getInstance(){
        return singleton;
    }

}

这种方式很多人都会写,但这样写单例模式有利有弊,优点是不用担心并发,静态变量只会初始化一次,缺点是如果引用了这个类的其他静态变量,就会创建该类的对象,但你可能永远都不会使用这个对象,造成内存的浪费,所以不推荐使用这种方式

2.懒汉式

public class Singleton {
    
    private static  Singleton singleton;

    private Singleton(){}

    public static Singleton  getInstance(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
    
}

懒汉式就不会有这个问题

刚开始学习 java 的时候,可能会这样写,但随着学习的深入,你会发现这样写有并发问题,试想线程1 刚判断完 singleton 为 null,准备去新建实例,线程2 也走到了判断 singleton 这一步,此时 singleton 还是空,线程2也新建了一个实例,这就不能保证单例了,因此懒汉式我们都是用下面这种方式

3.懒汉式(同步锁)

public class Singleton {

    private static  Singleton singleton;

    private Singleton(){}

    public static Singleton  getInstance(){
        if(singleton == null){
            synchronized (Singleton.class){
                if(singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

}

这种方式通过加同步锁保证对象的唯一性,这里我们判断了两次 singleton,原因是,如果线程1,2都在外层判断了 singleton 为空,线程1先拿到了同步锁,创建了一个新对象,线程2等待,线程1完成后,线程2也拿到了同步锁,如果内层不再判断 singleton 是否为空,线程2就会再次创建一个对象,这就无法保证对象的唯一性了。由于加了同步锁,这种方式在高并发下也能保证对象的唯一性。但这种写法还是会有一个问题,jvm 创建一个对象执行三步

1)堆内存开辟内存空间

2)在堆内存中实例化SingleTon里面的各个参数

3)对象指向堆内存空间

由于 jvm会优化指令,可能在执行第二步之前先执行了第三步,另一个线程走到这一步看到对象是非空的,直接拿来用,就会发生异常。后来 JDK1.5后,官方也发现了这个问题就发明了 volatile 关键字,下面这样写就没问题了

public class DCLSafeSingleton {
    
    //添加 volatile 关键字,防止指令重排
    private static volatile DCLSafeSingleton singleton;

    private DCLSafeSingleton() {}

    public static DCLSafeSingleton getInstance() {
        if (singleton == null) {
            synchronized (DCLSafeSingleton.class) {
                if (singleton == null) {
                    singleton = new DCLSafeSingleton();
                }
            }
        }
        return singleton;
    }
    
}

4.静态内部类

public class InnerClassSingleton{

    public static InnerClassSingleton getInstance() {
        return Instance.singleton;
    }

    private InnerClassSingleton(){}

    private static class Instance {
        static InnerClassSingleton singleton = new InnerClassSingleton();

    }

}

这种是算是比较好的单例模式写法了,静态内部类只加载一次,保证了线程安全,只有调用 getInstance 方法才会加载内部类,创建实例,不会造成内存的浪费。但这种方式有个缺点,由于是内部类形式,所以创建对象时无法传参。虽然这种方法看上去比较安全了,但可能存在反射攻击或反序列化攻击。如下面代码

反射攻击

public static void main(String[] args) throws Exception {
        InnerClassSingleton singleton = InnerClassSingleton.getInstance();
        Constructor<InnerClassSingleton> constructor = InnerClassSingleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        InnerClassSingleton newSingleton = constructor.newInstance();
        System.out.println(singleton == newSingleton);
    }

运行结果是 false

反序列化攻击

先引入依赖

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.8.1</version>
</dependency>

代码

public class Singleton implements Serializable {

    private static class Instance{
        private static Singleton instance = new Singleton();
    }

    private Singleton() {}

    public static Singleton getInstance() {
        return Instance.instance;
    }

    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        byte[] serialize = SerializationUtils.serialize(instance);
        Singleton newInstance = SerializationUtils.deserialize(serialize);
        System.out.println(instance == newInstance);
    }

}

运行结果也是 false

5.枚举法

《Effective java 》这本书写这是实现单例模式最好的方法。以上优点它都有,还不用担心有反射攻击或反序列化攻击,如下图

 

 

 调用方法:

public enum  EnumSingleton {
    
    INSTANCE;
    
    public void getSomething() {
        System.out.println("getSomething");
    }

    //调用方式
    public static void main(String[] args) {
        EnumSingleton.INSTANCE.getSomething();
    }

}

 

以上就是单例模式主要的创建方式啦,园友们可以根据具体业务场景进行选择合适的实现方式。

posted on
2019-09-28 11:02 fightingting 阅读() 评论() 编辑 收藏

 

版权声明:本文为fightingting原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/fightingting/p/10608996.html

设计模式--单例模式的实现方式的更多相关文章

随机推荐

  1. Devops实战(一)Docker的部署安装以及Docker-Compose的使用

    Docker的部署安装以及Docker-Compose的使用 1.docker和docker-Compose简 […]...

  2. 在linux下安装eclipse以及运行c++程序的安装步骤

    1.       下载jre,eclipse,cdt 其中jre是java运行环境,eclipse需要先装jr […]...

  3. 为什么有些公司的IT很乱?

    为什么很多公司甚至是闻名遐迩的资深IT公司,都被吐槽IT技术建设很烂呢?按惯例,问为什么之前,先问是不是。 — […]...

  4. 【WEB开发】微信网页授权第三方登录接口(WEB登录)

    随着手机微信的崛起,腾讯发布的微信联登确实很诱惑pc端的伙伴们,现在就说说在pc端用微信扫一扫实现微信第三方登 […]...

  5. java servlet的域对象

    在进行网络编程中的项目时 经常用到的域对象主要包括以下三种: 1、 ServletContext  作用范围比 […]...

  6. apache+php+mysql运行环境

            建议Apache2.4+php5.6+mysql5.5+phpmyadmin4.4.4  参考 […]...

  7. C# Monitor的Wait和Pulse方法使用详解 – 禅道

    C# Monitor的Wait和Pulse方法使用详解 【转载】http://blog.csdn.net/qq […]...

  8. JAVA从零学习 第一天 邮箱ych1102@163.com QQ382993199

    JAVA从零学习 第一天 邮箱ych1102@163.com QQ382993199 学习编程  听说读写 寻 […]...

展开目录

目录导航