单例模式

547 阅读4分钟

本篇涉及语言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;
}