概述
定义:一个类有且仅有一个实例,并且自行实例化向整个系统提供。
单例模式常见的写法:懒汉式、饿汉式。
单例模式的特点
- 单例类只能有一个实例
- 必须自己创建自己的唯一实例
- 必须自行向整个系统提供这个实例
饿汉式(立即加载)
特点:
- 饿汉式不管有没有调用getInstance()方法,都会预先在系统中创建一个静态对象
- 线程安全
优点:在多线程模式下是安全的
缺点:没有调用方法前就被加载,会占用内存
public class Singleton{
//饿汉式单例在类初始化的时候完成实例化
private static Singleton singleton = new Singleton();
//构造函数私有化
private Singleton(){
}
public static Singleton getInstance(){
return singleton;
}
}
懒汉式(延迟加载)
特点
- 懒汉式单例在第一次调用的时候实例化
- 线程不安全
优点:只有调用方法时才创建对象,不会占用内存
缺点:在多线程模式下不安全
public class Singleton{
//类定义声明
private static Singleton singleton;
//构造函数私有化
private Singleton(){
}
//懒汉式第一次调用时才创建对象,线程是不安全的
//当有多个请求同时调用getInstance()方法时,可能会产生多个实例
private static Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
改进:
双重校验锁,较常用,解决了线程问题和效率问题
public class Singleton{
private static Singleton singleton=null;
private Singleton(){
}
/**
* getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,
* 这样也是线程安全的,同时避免了每次都同步的性能损耗
**/
public static Singleton getInstance(){
if(singleton == null ){
synchronized (Singleton.class) {
if(singleton == null){
singleton = new Singleton() ;
}
}
}
return singleton;
}
}
静态内部类
class Singleton{
//静态内部类,只有静态内部类的静态属性被调用时,才会加载,可实现延时加载功能
private static class Inner{
private static final Singleton instance = new Singleton();
}
private Singleton() {
}
//方法没有同步,调用效率高
public static Singleton getInstance() {
return Inner.instance;
}
解释:线程安全是因为只要一个线程调用getInstance()方法,那么instance就是一个对象,而且用了static final修饰,保证了内存中只有这样一个实例存在,其它线程都是用instance对象。延迟加载因为静态内部类的静态内容只有被使用才会加载。高效是可以直接用,不需要等待,因为没有同步
单例模式的优点
- 提供了对唯一实例的受控访问。
- 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
- 允许可变数目的实例。
单例模式的使用场景
由于单例模式具有以上优点,并且形式上比较简单,所以是日常开发中用的比较多的一种设计模式,其核心在于为整个系统提供一个唯一的实例,其应用场景包括但不仅限于以下几种:
- 网站的计数器,一般也是采用单例模式实现,否则难以同步。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,用单例模式来维护,就可以大大降低这种损耗。
- 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
常见问题:
单例类可以被继承吗?
饿汉式单例和懒汉式单例由于构造方法是private的,所以他们都是不可继承的。
饿汉式单例好还是懒汉式单例好
在java中,饿汉式单例要优于懒汉式单例。C++中则一般使用懒汉式单例。