美文网首页
单例模式

单例模式

作者: 长点点 | 来源:发表于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;
}

相关文章

  • 【设计模式】单例模式

    单例模式 常用单例模式: 懒汉单例模式: 静态内部类单例模式: Android Application 中使用单例模式:

  • Android设计模式总结

    单例模式:饿汉单例模式://饿汉单例模式 懒汉单例模式: Double CheckLock(DCL)实现单例 Bu...

  • 2018-04-08php实战设计模式

    一、单例模式 单例模式是最经典的设计模式之一,到底什么是单例?单例模式适用场景是什么?单例模式如何设计?php中单...

  • 设计模式之单例模式详解

    设计模式之单例模式详解 单例模式写法大全,也许有你不知道的写法 导航 引言 什么是单例? 单例模式作用 单例模式的...

  • Telegram开源项目之单例模式

    NotificationCenter的单例模式 NotificationCenter的单例模式分析 这种单例模式是...

  • 单例模式Java篇

    单例设计模式- 饿汉式 单例设计模式 - 懒汉式 单例设计模式 - 懒汉式 - 多线程并发 单例设计模式 - 懒汉...

  • IOS单例模式的底层原理

    单例介绍 本文源码下载地址 1.什么是单例 说到单例首先要提到单例模式,因为单例模式是单例存在的目的 单例模式是一...

  • 单例

    iOS单例模式iOS之单例模式初探iOS单例详解

  • 单例模式

    单例模式1 单例模式2

  • java的单例模式

    饿汉单例模式 懒汉单例模式

网友评论

      本文标题:单例模式

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