美文网首页
单例模式

单例模式

作者: 长点点 | 来源:发表于2022-05-01 17:11 被阅读0次

    本篇涉及语言java kotlin c++
    概念最简单的一个设计模式,但是实现起来还是有很多需要注意的地方。而且也是被常被不合时宜使用的设计模式,下面会先看一下使用场景,再去展开说明实现方式

    一、单例和静态类

    意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

    主要解决:一个全局使用的类频繁地创建与销毁。

    何时使用:当您想控制实例数目,节省系统资源的时候。

    如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

    关键代码:构造函数是私有的。

    静态类也有相似的功能,下面比较一下两者区别。

    名称 优点 缺点 适用场景
    单例 可以继承,实现接口,覆写,懒加载 内存难被清理回收 必须有且只有一个对象的场景(例如:log系统,线程池)
    静态类 产生对象会随静态方法执行完而被释放 没有面向对象特性 工具类

    二、单例的实现方式

    java

    1.饿汉模式(线程安全)

    public class Singleton {
    
        private static Singleton instance = new Singleton();
    
        private Singleton() {}
    
        public static Singleton getInstance() {
            return instance;
        }
    
    } 
    

    2.懒汉模式

    (1)非线程安全实现

    public class Singleton {
    
        private static Singleton instance;
    
        private Singleton() {}
    
        public static Singleton getInstance() {
            if (instance == null) {
                instance =  new Singleton();
            }
    
            return instance;
        }
        
    } 
    

    (2)线程安全实现
    双检锁/双重校验锁

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

    (3)登记式/静态内部类(线程安全)

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

    (4)枚举(线程安全)

    public enum Singleton6 {
    
        INSTANCE;
    
        public void whateverMethod() {}
    
    }
    

    (5)使用ThreadLocal(线程安全)

    public class Singleton {
    
        private static final ThreadLocal<Singleton> tlSingleton = new ThreadLocal<Singleton>() {
            @Override
            protected Singleton initialValue() {
                return new Singleton();
            }
        };
    
        private Singleton() {}
    
        public static Singleton getInstance() {
            return tlSingleton.get();
        }
        
    }
    

    (6)使用CAS锁(线程安全)

    public class Singleton {
    
        private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>();
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            for (; ; ) {
                Singleton current = INSTANCE.get();
    
                if (current != null) {
                    return current;
                }
    
                current = new Singleton();
    
                if (INSTANCE.compareAndSet(null, current)) {
                    return current;
                }
            }
        }
    
    }
    
    名称 是否懒加载 是否线程安全 优点 缺点 适用场景
    懒汉式线程不安全 实现简单 非线程安全 不建议使用
    懒汉式线程安全 实现简单;第一次调用才初始化,避免内存浪费 加锁会影响效率 不建议使用
    饿汉式 实现简单;没有加锁,执行效率高 类加载时就初始化,浪费内存 默认情况下推荐使用(没有懒加载需求,也不考虑反序列化)
    登记式/静态内部类 兼顾运行效率和懒加载需求 / 有懒加载需求情况下,默认使用方案
    枚举 实现简单(面试的时候写代码可以快人一步,哈哈); 自动支持序列化机制 不能用反射调用私有构造函数 Effective Java 作者 Josh Bloch 提倡的方式,感觉面试用的更多一些
    使用ThreadLocal 多了解一个知识点,ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。 实现复杂 面试
    使用CAS锁 多了解一个知识点 实现复杂 面试

    kotlin

    (1)object关键字的饿汉模式

    object Singleton{}
    

    用as转成字节码再反编译后的java代码,可以看出是饿汉模式

    public final class Singleton{
       public static final Singleton INSTANCE;
       private Singleton(){}
       static {
          Singletonvar0 = new Singleton();
          INSTANCE = var0;
       }
    }
    

    除了object实现饿汉模式之外,其他和java形式雷同。

    C++

    c++除了私有构造函数,还要注意赋值拷贝接口,内存安全的问题

    • 全局只有一个实例:static 特性,同时禁止用户自己声明并定义实例(把构造函数设为 private)
    • 线程安全
    • 禁止赋值和拷贝(操作符重载)
    • 用户通过接口获取实例:使用 static 类成员函数()

    (1)线程安全,内存安全的懒汉模式(智能指针,锁)

    #include <iostream>
    #include <memory> // shared_ptr
    #include <mutex>  // mutex
    
    // version 2:
    // with problems below fixed:
    // 1. thread is safe now
    // 2. memory doesn't leak
    
    class Singleton{
    public:
        typedef std::shared_ptr<Singleton> Ptr;
        ~Singleton(){
            std::cout<<"destructor called!"<<std::endl;
        }
        Singleton(Singleton&)=delete;
        Singleton& operator=(const Singleton&)=delete;
        static Ptr get_instance(){
    
            // "double checked lock"
            if(m_instance_ptr==nullptr){
             //只有判断指针为空的时候才加
             //避免每次调用 get_instance的方法都加锁
             //锁的开销毕竟还是有点大的
                std::lock_guard<std::mutex> lk(m_mutex);
                if(m_instance_ptr == nullptr){
                  m_instance_ptr = std::shared_ptr<Singleton>(new Singleton);
                  //m_instance_ptr析构时,new出的对象也会被delete掉
                }
            }
            return m_instance_ptr;
        }
    
    
    private:
        Singleton(){
            std::cout<<"constructor called!"<<std::endl;
        }
        static Ptr m_instance_ptr;
        static std::mutex m_mutex;
        //Singleton(const A&); //拷贝构造函数,C++11之前delete的替代方案
        //Singleton& operator=(const A&);//拷贝复制运算符,C++11之前delete的替代方案
    };
    
    // initialization static variables out of class
    Singleton::Ptr Singleton::m_instance_ptr = nullptr;
    std::mutex Singleton::m_mutex;
    
    int main(){
        Singleton::Ptr instance = Singleton::get_instance();
        Singleton::Ptr instance2 = Singleton::get_instance();
        return 0;
    }
    
    
    

    (2)局部静态变量 懒汉式模式(推荐方式)

    #include <iostream>
    
    class Singleton
    {
    public:
        ~Singleton(){
            std::cout<<"destructor called!"<<std::endl;
        }
        Singleton(const Singleton&)=delete;
        Singleton& operator=(const Singleton&)=delete;
        static Singleton& get_instance(){
            static Singleton instance;
            return instance;
    
        }
    private:
        Singleton(){
            std::cout<<"constructor called!"<<std::endl;
        }
    };
    
    int main(int argc, char *argv[])
    {
        Singleton& instance_1 = Singleton::get_instance();
        Singleton& instance_2 = Singleton::get_instance();
        return 0;
    }
    
    
    

    这种方法又叫做 Meyers' SingletonMeyer's的单例, 是著名的写出《Effective C++》系列书籍的作者 Meyers 提出的。所用到的特性是在C++11标准中的Magic Static特性:

    • 通过局部静态变量的特性保证了线程安全 (C++11, GCC > 4.3, VS2015支持该特性);
    • 不需要使用共享指针,代码简洁;
    • 注意在使用的时候需要声明单例的引用 Single& 才能获取对象。

    (3)c++ call_once

    在C++11中提供一种方法,使得函数可以线程安全的只调用一次。即使用std::call_once和std::once_flag。std::call_once是一种lazy load的很简单易用的机制。实现代码如下:

    #include <iostream>
    #include <memory> // shared_ptr
    #include <mutex>  // mutex
    
    class Singleton
    {
    public:
        ~Singleton(){
            std::cout<<"destructor called!"<<std::endl;
        }
        Singleton(const Singleton&)=delete;
        Singleton& operator=(const Singleton&)=delete;
        static Singleton& get_instance(){
            static std::once_flag s_flag;
            std::call_once(s_flag,[&](){
            instance_.reset(new Singleton);
            });
            return *instance_;
        }
    private:
        Singleton(){
            std::cout<<"constructor called!"<<std::endl;
        }
    };
    
    int main(int argc, char *argv[])
    {
        Singleton& instance_1 = Singleton::get_instance();
        Singleton& instance_2 = Singleton::get_instance();
        return 0;
    }
    

    相关文章

      网友评论

          本文标题:单例模式

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