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