zoukankan      html  css  js  c++  java
  • 设计模式单例模式的实现方式

    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();
        }
    
    }

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

  • 相关阅读:
    linux基础
    模块三、企业实战案例
    模块二、shell脚本逻辑结构
    模块一:shell 脚本基础
    三剑客、shell脚本
    定时任务、用户管理、磁盘介绍
    python笔记03
    文件属性、正则表达式、文件权限
    Linux系统目录结构介绍
    Linux基础及入门介绍
  • 原文地址:https://www.cnblogs.com/fightingting/p/10608996.html
Copyright © 2011-2022 走看看