Java和C++实现单例设计模式

一、单例设计模式概述

单例模式,也叫单子模式,是一种常用的软件设计模式,属于创建型模式的一种。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。----引述维基百科。

简单来说,单例设计模式的作用,就是希望设计一个类,这个类从始到终只允许被创建一个对象。

二、单例设计模式实现的过程

  1. 要求一个类只能被创建一个类对象,则在设计这个类的时候,需要把这个类的构造函数的控制起来,不让这个类的使用者通过构造函数来构造这个类对象,所以把这个的类的构造函数设置为private访问权限。
  2. 既然已经设计将类的构造函数设置为private,不让这个类的用户使用构造函数来创建类对象,则需要为这个类的用户提供一个返回这个类对象的方法。
  3. 返回类对象的方法,需要的满足多次调用这个方法的时候,从始到终返回的都是同一个类对象。
  4. 更进一步,就是要考虑类对象的析构(资源有没有被释放)、线程同步等问题了。

三、使用Java实现单例设计模式

  1. 饿汉式的单例设计模式
    所谓的饿汉式,就是先把类对象创建出来,赋给一个类的私有成员变量。用java实现饿汉式的单例设计模式的代码如下:
class Singleton
{
	private Singleton() {}   //1.类构造函数私有
	int flag = 0;
	private static Singleton obj = new Singleton();  //私有成员变量,创建类对象
	
	void setFlag(int n)  {
		flag = n;
	}
	
	int getFlag()	{
		return flag;
	}
	
	static Singleton getInstance()  {//2.返回类对象的一个方法,由于static限定,可以通过类名调用这个函数
		System.out.println("this is a singleton demo.");
		return obj;
	}
	
	public static void main(String []args)
	{
		Singleton single = Singleton.getInstance();  
		single.setFlag(100);
	
		Singleton single2 = Singleton.getInstance();
		System.out.println("--------------\n" + single2.getFlag());
	}
}

运行这个程序,得到的结果为:
在这里插入图片描述
打印single2对象的Flag的值,得到的是single对象设置的值,由此说明了single对象和single2对象实质是同一个对象。
进一步分析,由于饿汉式的单例设计模式中创建类对象private static Singleton obj = new Singleton();是一个原子操作,只能创建出一个类对象,所以饿汉式的单例设计模式是线程安全的,而且简单,所以在工作中更多是使用这个单例设计模式,缺点就是单例对象一直都存在内存中,占点内存。

  1. 懒汉式的单例设计模式
    所谓的懒汉式,就是不到使用单例对象的时候,就不创建单例对象。
    使用Java实现懒汉式设计模式的代码如下:
public class Singleton {
    private static volatile Singleton obj = null;
  
    // Private constructor suppresses 
    // default public constructor
    private Singleton() {};
  
    //Thread safe and performance  promote 
    public static  Singleton getInstance() {
        if(obj == null){
             synchronized(Singleton.class){
                 // When more than two threads run into the first null check same time, 
                 // to avoid instanced more than one time, it needs to be checked again.
                 if(obj == null){ 
                     obj = new Singleton();
                  }
              } 
        }
        return obj;
    }
  }

使用双if来确保线程同步,创建出来的对象只有一个。这里使用了synchronized()函数来实现线程同步,需要付出的代价是多消耗系统资源。

四、使用C++实现单例设计模式

使用C++设计这样一个类如下:

#include <iostream>
// version1:
// with problems below:
// 1. thread is not safe
// 2. memory leak

class Singleton{
private:
    Singleton(){
        std::cout<<"constructor called!"<<std::endl;
    }
    Singleton(Singleton&)=delete;
    Singleton& operator=(const Singleton&)=delete;
    static Singleton* m_instance_ptr;
public:
    ~Singleton(){
        std::cout<<"destructor called!"<<std::endl;
    }
    static Singleton* get_instance(){
        if(m_instance_ptr==nullptr){
              m_instance_ptr = new Singleton;
        }
        return m_instance_ptr;
    }
    void use() const { std::cout << "in use" << std::endl; }
};

Singleton* Singleton::m_instance_ptr = nullptr;

int main(){
    Singleton* instance = Singleton::get_instance();
    Singleton* instance_2 = Singleton::get_instance();
    printf("instance pointer:%p,instance_2 pointer:%p\n", instance. instance_2);
    return 0;
}

可以看到,两个指针的内容是一样,获取了两次类的实例,却只有一次类的构造函数被调用,表明只生成了唯一实例,这个是C++最基础版本的单例实现,它有哪些问题呢?

  1. 线程安全的问题,当多线程获取单例时有可能引发竞态条件:第一个线程在if中判断 m_instance_ptr是空的,于是开始实例化单例;同时第2个线程也尝试获取单例,这个时候判断m_instance_ptr还是空的,于是也开始实例化单例;这样就会实例化出两个对象,这就是线程安全问题的由来;解决办法:加锁
  2. 内存泄漏. 注意到类中只负责new出对象,却没有负责delete对象,因此只有构造函数被调用,析构函数却没有被调用;因此会导致内存泄漏。解决办法: 使用共享指针;

因此,这里提供一个改进的,线程安全的、使用智能指针的实现;

#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){
            std::lock_guard<std::mutex> lk(m_mutex);
            if(m_instance_ptr == nullptr){
              m_instance_ptr = std::shared_ptr<Singleton>(new Singleton);
            }
        }
        return m_instance_ptr;
    }


private:
    Singleton(){
        std::cout<<"constructor called!"<<std::endl;
    }
    static Ptr m_instance_ptr;
    static std::mutex m_mutex;
};

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

shared_ptr和mutex都是C++11的标准,以上这种方法的优点是

  • 基于 shared_ptr, 用了C++比较倡导的 RAII思想,用对象管理资源,当 shared_ptr 析构的时候,new 出来的对象也会被 delete掉。以此避免内存泄漏。
  • 加了锁,使用互斥量来达到线程安全。这里使用了两个 if判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,避免每次调用 get_instance的方法都加锁,锁的开销毕竟还是有点大的。
    不足之处在于: 使用智能指针会要求用户也得使用智能指针,非必要不应该提出这种约束; 使用锁也有开销; 同时代码量也增多了,实现上我们希望越简单越好。

最推荐的懒汉式单例(magic static )——局部静态变量

#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, GCC > 4.3, VS2013支持该特性);
  • 不需要使用共享指针,代码简洁;
  • 注意在使用的时候需要声明单例的引用 Single& 才能获取对象

五、总结

单例设计模式是工作中比较常用的一种设计模式,相对于其他的设计模式,单例设计模式还是简单的,但应用单例设计模式的时候,还是有一些容易出错的点需要注意的,比如饿汉式和懒汉式的优劣之处,线程同步怎样实现等。

参考:参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值