单例模式

作者: SheHuan | 来源:发表于2016-09-14 16:00 被阅读868次

    使用场景

    实际的开发中,为了避免创建多个对象消耗过多的资源,或者某个类的对象只能有一个,所以就需要使用单例模式来确保某个类只能对外提供一个对象。

    特点

    • 类的构造函数一般用private修饰,不对外公开
    • 一般通过一个静态方法返回单例对象
    • 必须保证线程安全,即在多线程场景下能确保只有一个单例对象

    实现方式

    1、懒汉单例模式
    public class Singleton{
            private static Singleton instance;
            private Singleton(){  
            }
            
            public static synchronized Singleton getInstance(){
                if (instance == null){
                    instance = new Singleton();
                }
                return instance;
            }
        }
    

    懒汉单例模式只有在第一次使用时才会初始化单例,一定程度上能节约资源,但反应会稍慢;通过synchronized关键字,保证了在多线程情况下单例的唯一性,但是在单例被第一次初始化后,再调用getInstance()方法还需要进行同步操作,这样会造成不必的系统开销。

    2、双重检查锁定单例模式(Double Check Lock)
    public class Singleton {
        private volatile static Singleton instance;
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            if (instance == null) {
                synchronized (Singleton.class) {
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    

    在getInstance()中,首先对instance进行非空判断,避免多余的同步,这也解决了懒汉单例模式中每次同步的问题,接下来如果instance为空则创建其实例,当然这一步需要保证同步操作。

    但这里有个隐藏问题,注意instance = new Singleton();这行代码,它的执行可以分解为第三个步骤:(1)为instance实例分配内存。(2)执行Singleton构造函数来初始化instance。(3)将instance指向分配的内存。

    但在JDK1.5前,上边的(2)(3)无法保证按顺序执行,如果按(1)(3)(2)顺序,假如A线程执行完(3),(2)未执行就被切换到B线程,因为步骤(3)已经在A线程执行,则B线程直接取走了认为非空instance,这就导致双重检查锁定的判断失效。

    在JDK1.5后,只要这样声明instance实:private volatile static Singleton instance;即添加volatile修饰符,这样就可以保证instance每次都从主内存读取,避免了上边的问题,但会略影响性能。这种单例模式也是在第一次执行getInstance()时创建单例,但第一次反映稍慢。

    这种方式目前使用的较多。

    3、静态内部类单例模式
    public class Singleton {
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            return SingletonHolder.instance;
        }
    
        private static class SingletonHolder {
            private static final Singleton instance = new Singleton();
        }
    }
    

    这种方式只有在的第一次调用getInstance()方法时,虚拟机才会加载SingletonHolder类,并初始化instance实例,即保证了线程同步,也能保证单例的唯一性,相对双重检查锁定单例模式简单了许多,推荐使用这种方式来实现单例模式。

    4、容器单例模式
    public class SingletonManager {
        private static Map<String, Object> instanceMap = new HashMap<>();
    
        private SingletonManager() {
        }
    
        public static void addInstance(String key, Object instance) {
            if (!instanceMap.containsKey(key)) {
                instanceMap.put(key, instance);
            }
        }
    
        public Object getInstance(String key) {
            return instanceMap.get(key);
        }
    }
    

    采用Map集合管理对象的实例,保证实例的唯一性,这种方式多用于管理多种类的实例场景,同时你的类并不一定需要实现单例机制,因为SingletonManager可以解决这个问题。你只需在初始化时创建对应类的实例并调用addInstance(String key, Object instance)来进行保存,使用时调用getInstance(String key),即可根据key得到对应类的实例。

    5、枚举单例模式

    public enum Singleton {
        INSTANCE;
        public void doSomething(){
            //
        }
    }
    

    这种写法相对最简单,并且枚举实例的创建是线程安全的,并且任何情况只有一个单例。

    相关文章

      网友评论

      • 63479f7aa2c3:请问如下写,会有什么问题吗?
        public class Singleton{
        private static Singleton instance = new Singleton();
        private Singleton(){
        }

        public static Singleton getInstance(){
        return instance;
        }
        }
        63479f7aa2c3:@VipOthershe 多谢指教
        SheHuan:@野草的春天 这是饿汉式单例,Singleton类加载时就会创建instance实例,这样就需要提前开销一部分系统资源。如果该实例在后期未使用,岂不是浪费了系统资源。其它问题不存在。
      • 极乐君:楼主,我能转到我的网站吗?http://www.dreawer.com
        极乐君:@VipOthershe 谢谢~ :+1:
        SheHuan: @极乐网络 可以的
      • Blazer:很好

      本文标题:单例模式

      本文链接:https://www.haomeiwen.com/subject/bgnjettx.html