单例模式(Singleton Pattern)应该是设计模式里面最简单的一种模式了,属于创建型模式,我也从单例模式开始,开启设计模式之旅。
(创建型模式:单例模式、工厂(方法)模式、抽象工厂模式、建造者模式、原型模式)
定义:Ensure a class has only one instance, and provide a global point of access to it.确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
简单来说,单例对象的类必须保证只有一个实例存在。
应用场景:
- Windows的任务管理器就是很典型的单例模式,想想看,你能打开两个任务管理器吗?
- Windows的回收站也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
- 网站的计数器,一般也是采用单例模式实现。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
- 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
单例模式类图:
单例模式三要素:
- 私有的构造方法;
- 指向自己实例的私有静态引用;
- 以自己实例为返回值的静态的公有方法。
懒汉模式:
package com.clarezhou.singleton;
public class LangHanSingleton {
//定义一个私有类变量来存放单例,私有的目的是指外部无法直接获取这个变量,而要使用提供的公共方法来获取
private static LangHanSingleton lhs = null;
//定义私有构造器,表示只在类内部使用,亦指单例的实例只能在单例类内部创建
private LangHanSingleton(){}
//定义一个公共的公开的方法来返回该类的实例,由于是懒汉式,需要在第一次使用时生成实例,所以为了线程安全,使用synchronized关键字来确保只会生成单例
public static synchronized LangHanSingleton getInstance(){
if(lhs == null){
lhs = new LangHanSingleton();
}
return lhs;
}
}
此模式单线程是安全的,但是多线程的情况下可能会同时创建多个实例对象,所以我们在getInstance公共方法上加了synchonized同步锁,因此多线程的情况下每次都需要进行同步,开销就加了不少。
饿汉模式(推荐):
package com.clarezhou.singleton;
public class EHanSingleton {
//此处定义类变量实例并直接实例化,在类加载的时候就完成了实例化并保存在类中
private static EHanSingleton ehs = new EHanSingleton();
//定义无参构造器
private EHanSingleton(){}
//定义公开方法,返回已创建的单例
public static EHanSingleton getInstance(){
return ehs;
}
}
此模式是线程安全的,而且在类加载的同时会创建实例对象,反应速度较快。
双重检测:
package com.clarezhou.singleton;
public class DoubleSingleton {
//定义一个私有类变量
private static DoubleSingleton ds = null;
//定义无参构造器
private DoubleSingleton(){}
///定义公开方法
public static DoubleSingleton getInstance(){
if(ds == null){
synchronized (DoubleSingleton.class) {
if(ds == null){
ds = new DoubleSingleton();
}
}
}
return ds;
}
}
双重检测较好的解决了懒汉模式开销大的问题,但首次加载的时候效率依然较低。
静态内部类方式:
package com.clarezhou.singleton;
public class InsideSingleton {
//定义内部类
public static class SingletonHolder{
private static InsideSingleton ins = new InsideSingleton();
}
//定义无参构造器
private InsideSingleton(){}
//公共方法
public static InsideSingleton getInstance(){
return SingletonHolder.ins;
}
}
加载外部类的时候,并不会同时加载其静态内部类,只有在发生调用的时候才会进行加载,加载的时候就会创建单例实例并返回,有效实现了懒加载(延迟加载),至于同步问题,我们采用和饿汉式同样的静态初始化器的方式,借助JVM来实现线程安全。