什么单例模式 ?
单例模式,也称单子模式,属于一种创建型模式。在这种模式下,保证一个类只能有一个实例对象,而且该类负责创建唯一的实例对象。
设计原则:构造方法私有化、静态方法返回对象、对象只进行一次实例化
例如有一个 SingleObject 类,通过 SingleObject.getInstance() 返回的所有对象的引用,都是完全相同的。
public class SingleObject {
// 构造函数私有化
private SingleObject() {}
// 对象只进行一次实例化
private static final SingleObject instance = new SingleObject();
// 静态方法返回对象
public static SingleObject getInstance(){
return instance;
}
}
为什么要使用单例模式 ?
一、节约资源
单例模式,保证内存中只有一个实例对象,一方面节省内存空间;另一方面避免进行重复的对象创建和销毁造成资源的浪费。
例如,以前的打井取水就是一种单例模式的实现,我们只需要打一次井,就可以一直取水。而不是每次重新打井,取水结束就把井给堵回去。其实,井 相当于一个单例对象,打井
就是一次实例化的过程,每次 取水
就是调用这个单例对象的方法。
日常开发中,单例模式的应用场景有很多,例如:
数据库连接很耗资源,每次请求数据库都重新建立一次连接,显然很浪费资源。运用单例模式,实现一个数据库连接池,确保数据库驱动的连接类只被初始化一次,可以多次使用。
默认情况下,Spring 容器中 Bean 的作用域是单例的,减少 Bean 实例实例化的消耗和 JVM 垃圾回收,以及可以快速获取 Bean 的实例对象。
线程池的实现。
二、方便管理
例如,一些工具类,只需要一个实例来实现功能:
生成唯一序列号
网站的计数器,基于 Web 应用下,保证数值的同
三、避免多重占用
单例模式可以避免对资源的多重占用,例如 应用程序的日志应用,保证只有一个实例操作日志文件,避免日志文件被多个实例打开造成异常。
四、单例模式的缺点
1、扩展性差,想要进行扩展,通常只能修改代码。
2、单例模式与单一职责原则有冲突
怎么实现单例模式 ?
单例模式的实现方式有很多种,从广义的角度分为 饿汉式 和 懒汉式 。
饿汉式分为:普通饿汉式、枚举方式。
懒汉式分为:普通懒汉式、静态内部类、双重检验锁。
接下来,分别讲述这五种单例模式的具体实现方式。
普通饿汉式
public class SingleObject {
// 构造函数私有化
private SingleObject() {}
// 创建实例对象
// final:防止对象被修改,保证线程安全
private static final SingleObject instance = new SingleObject();
// 静态工厂方法,返回单例的实例对象
public static SingleObject getInstance() {
return instance;
}
}
优点:
1、单例对象的创建是线程安全的,类加载的时候进行就会对单例对象初始化
2、首次获取单例对象的时候不需要加锁
缺点:
1、单例对象的创建不是延迟加载
2、反射可以破坏单例模式的约束,重复构建对象
枚举方式
public enum SingleObject {
INSTANCE;
}
优点:
1、线程安全,类加载的时候进行就会对单例对象初始化
2、可以防止反射获取枚举类的私有构造方法
3、可以防止序列化破环单例模式
缺点:
1、单例对象的创建不是延迟加载
普通懒汉式
public class SingleObject {
// 构造函数私有化
private SingleObject() {}
// 创建实例对象
private static SingleObject instance = null;
// 静态工厂方法,返回单例的实例对象
public static SingleObject getInstance() {
if(instance == null) {
instance = new SingleObject();
}
return instance;
}
}
优点:
1、延迟加载
缺点:
1、线程不安全,会创建出多个实例
2、反射可以破坏单例模式的约束,重复构建对象
静态内部类
public class SingleObject {
// 构造函数私有化
private SingleObject() {}
private static SingleInner {
private static final SingleObject instance = new SingleObject();
}
public static SingleObject getInstance() {
return SingleInner.instance;
}
}
优点:
1、延迟加载
2、线程安全,只有第一次调用 getInstance()
方法的时候,虚拟机才会加载 SingletonHolder
类,初始化单例对象 instance
3、单例对象的初始化不需要加锁。
缺点:
1、传参问题,单例对象是通过静态内部类进行初始化,外部无法传递参数进去
2、反射可以破坏单例模式的约束,重复构建对象
双重校验锁
public class SingleObject {
// 构造函数私有化
private SingleObject() {}
// 创建实例对象
// volatile:防止变量访问前后的指令重排,保证指令的执行顺序
private volatile static SingleObject instance = null;
// 静态工厂方法,返回单例的实例对象
public static SingleObject getInstance() {
// 第一重校验
if(instance == null) {
// 同步锁,注意这里是类级别的锁
synchronized (SingleObject.class) {
// 第二重校验
if(instance == null) {
instance = new SingleObject();
}
}
}
return instance;
}
}
优点:
1、延迟加载
2、线程安全,单例对象的初始化
3、单例对象的获取不需要加锁。
缺点:
1、反射可以破坏单例模式的约束,重复构建对象