zoukankan      html  css  js  c++  java
  • java中全面的单例模式多种实现方式总结

    单例模式的思想

    想整理一些 java 并发相关的知识,不知道从哪开始,想起了单例模式中要考虑的线程安全,就从单例模式开始吧。
    以前写过单例模式,这里再重新汇总补充整理一下,单例模式的多种实现。

    单例模式那件小事,看了你不会后悔
    单例模式不是一件小事,快回来看看

    之前在第一篇文章说,单例模式的主要思想是:

    • 将构造方法私有化( 声明为 private ),这样外界不能随意 new 出新的实例对象;
    • 声明一个私有的静态的实例对象,供外界使用;
    • 提供一个公开的方法,让外界获得该类的实例对象

    这种说法看上去没错,但也好像不太准确。其实,就算外界能随意 new 出新的实例对象,但只要我们保证我们每次使用的对象是唯一的,就可以。

    单例模式的 N 种实现方式

    饿汉式(线程安全,可用)

    public class Singleton {
        private Singleton() {
        }
    
        private static Singleton sSingleton = new Singleton();
    
        public static Singleton getInstance() {
            return sSingleton;
        }
    }
    
    • 缺点: 类一加载的时候,就实例化,提前占用了系统资源。

    常量式(线程安全,可用)

    public class Singleton {
        private Singleton() {
        }
    
        public static final Singleton sSingleton = new Singleton();
    }
    

    将实例对象用 public static final 修饰,不提供公开方法获取实例,直接通过 Singleton.sSingleton 获取。

    • 缺点:与饿汉式一样,类一加载的时候,就实例化,提前占用了系统资源。

    懒汉式(线程不安全,并发场景不可用)

    public class Singleton {
        private Singleton() {
        }
    
        private static Singleton sSingleton;
    
        public static Singleton getInstance() {
            if (sSingleton == null) {
                sSingleton = new Singleton();
            }
            return sSingleton;
        }
    }
    
    • 缺点:第一次第一次加载时反应稍慢,线程不安全。

    同步的懒汉式?(线程安全,可用,不建议使用)

    public class Singleton {
        private Singleton() {
        }
    
        private static Singleton sSingleton;
    
        public synchronized static Singleton getInstance() {
            if (sSingleton == null) {
                sSingleton = new Singleton();
            }
            return sSingleton;
        }
    }
    
    • 缺点:第一次加载时反应稍慢,每次调用 getInstance 都进行同步,造成不必要的同步开销,这种模式一般不建议使用。

    双重检查锁 DCL (线程安全,大多数场景满足需求,推荐使用)

    public class Singleton {
        private Singleton() {
        }
    
        /**
         * volatile is since JDK5
         */
        private static volatile Singleton sSingleton;
    
        public static Singleton getInstance() {
            if (sSingleton == null) {
                synchronized (Singleton.class) {
                    // 未初始化,则初始instance变量
                    if (sSingleton == null) {
                        sSingleton = new Singleton();
                    }
                }
            }
            return sSingleton;
        }
    }
    

    sSingleton = new Singleton() 不是一个原子操作。(XXX)故须加 volatile 关键字修饰,该关键字在 jdk1.5 之后版本才有。

    • 优点:资源利用率高,第一次执行getInstance时单例对象才会被实例化,效率高。
    • 缺点:第一次加载时反应稍慢,也由于Java内存模型的原因偶尔会失败。在高并发环境下也有一定的缺陷,虽然发生的概率很小。DCL模式是使用最多的单例实现方式,它能够在需要时才实例化单例对象,并且能够在绝大多数场景下保证单例对象的唯一性,除非你的代码在并发场景比较复杂或者低于jdk1.6版本下使用,否则这种方式一般能够满足需求。

    静态内部类(线程安全,推荐使用)

    public class Singleton {
    
        private Singleton () {
        }
    
        private static class InnerClassSingleton {
         private final static Singleton sSingleton = new Singleton();
        }
    
        public static Singleton getInstance() {
            return InnerClassSingleton.sSingleton;
        }
    }
    

    优点:推荐使用。

    枚举单例(线程安全,不建议使用)

    public enum Singleton{
        INSTANCE;
        
        // 其它方法
        public void doSomething(){
            ...
        }
    }
    
    • 优点:枚举实现单例很简单,也很安全。
    • 缺点:经验丰富的 Android 开发人员都会尽量避免使用枚举。官方文档有说明:相比于静态常量Enum会花费两倍以上的内存。

    另类实现——利用容器实现单例

    import java.util.HashMap;
    import java.util.Map;
    
    public class Singleton {
        private static Map<String, Object> objMap = new HashMap<String, Object>();
    
        private Singleton() {
        }
    
        public static void registerService(String key, Object instance) {
            if (!objMap.containsKey(key)) {
                objMap.put(key, instance);
            }
        }
    
        public static Object getService(String key) {
            return objMap.get(key);
        }
    }
    

    利用了 HashMap 容器 key 不可重复的特性。

    • 优点:这种实现方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一接口进行获取操作,降低用户使用成本,也对用户隐藏了具体实现,降低耦合度。
    • 缺点:没有私有化构造方法,用户可以 new 出新的实例对象。

    防止反射破坏单例

    前面的多种实现方法中,很多我们按照构造方法私有化的思想来实现的,我们知道,利用反射,仍然可以创建出新对象,这样在反射场景中,这种思想实现的单例模式就失效了,那么如何防止反射破坏单例模式呢?原理上就是在存在一个实例的情况下,再次调用构造方法时,抛出异常。下面以静态内部类的单例模式为例:

    public class Singleton {
        private static boolean flag = false;  
      
        private Singleton(){  
            synchronized(Singleton.class)  
            {  
                if(flag == false)  
                {  
                    flag = !flag;  
                }  
                else  
                {  
                    throw new RuntimeException("单例模式被侵犯!");  
                }  
            }  
        }  
    
        private static class InnerClassSingleton {
         private final static Singleton sSingleton = new Singleton();
        }
    
        public static Singleton getInstance() {
            return InnerClassSingleton.sSingleton;
        }
    }
    

    具体测试代码,见 单例模式不是一件小事,快回来看看

    防止序列化和反序列化破坏单例

    通过序列化可以讲一个对象实例写入到磁盘中,通过反序列化再读取回来的时候,即便构造方法是私有的,也依然可以通过特殊的途径,创建出一个新的实例,相当于调用了该类的构造函数。要避免这个问题,我们需要在代码中加入如下方法,让其在反序列化过程中执行 readResolve 方法时返回 sSingleton 对象。

    private Object readResolve() throws ObjectStreamException {
        return sSingleton;
    }
    

    结语

    有没有一种方式实现的单例模式在任何情况下都是一个单例呢?

    ——
    有。就是上面说的枚举单例。枚举,就能保证在任何情况下都是单例的,并且是线程安全的。

  • 相关阅读:
    React元素渲染
    初识JSX
    微信小程序复制文本到剪切板
    微信小程序报错request:fail url not in domain list
    小程序,通过自定义编译条件,模拟推荐人功能
    积分抵扣逻辑
    微信小程序 switch 样式
    tomcat 配置开启 APR 模式
    tomcat8 传输json 报错 Invalid character found in the request target. The valid characters are defined in RFC 3986
    c++数组初始化误区
  • 原文地址:https://www.cnblogs.com/joy99/p/9859764.html
Copyright © 2011-2022 走看看