单例模式

本篇涉及语言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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容