「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」
单例设计模式
1.单例设计模式的理解
- 即某个类在全局系统中,有且仅有一个实例,并且该类提供一个方法,使得其它类可以访问该实例。 注意
- 单例类全局仅会创建一个对象实例。
- 该类必须提供一个取得该唯一实例的方法(静态方法)
2.单例模式的区分 -- 饿汉式
2.1 优缺点
优点: 实现方式比较简单, 通常来说只需要使用静态变量,或者放在静态代码块中即可实现。它在类装载的过程中就完成实例化,从jvm层面帮我们解决了线程安全问题
缺点: 在类装载时就完成实例化,没有达到 LazyLoading(懒加载)的效果,容易造成内存浪费。
2.2 举例
1.静态变量形式
//饿汉式(静态变量)
class Singleton {
//1. 构造器私有化, 外部不能new
private Singleton() {
}
//2.本类内部创建对象实例
private final static Singleton instance = new Singleton();
//3. 提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}
}
2.静态代码块形式
class Singleton {
//1. 构造器私有化, 外部能new
private Singleton() {
}
//2.本类内部创建对象实例
private static Singleton instance;
static { // 在静态代码块中,创建单例对象
instance = new Singleton();
}
//3. 提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}
}
总结:上面这两种方法的实现本质上都一样, 这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,在单例模式中大多数都是调用getInstance方法, 但是导致类装载 的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance就没有达到lazy loading的效果,在生产环境中可以用,但一般不推荐这种方式,容易造成内存浪费。
3.单例模式 -- 懒汉式
3.1 懒汉式(线程不安全)
class Singleton {
private static Singleton instance;
private Singleton() {}
//提供一个静态的公有方法,当使用到该方法时,才去创建 instance
//即懒汉式
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点: 达到懒加载的效果,避免内存浪费,执行效率高
缺点: 只能在单线程环境下使用,在多线程情况下,线程不安全,容易创建多个对象实例
3.2 懒汉式(线程安全 -- synchronized关键字)
class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点: 达到懒加载的效果,避免内存浪费,线程安全
缺点: 执行效率极低,单例对象通常使用比较多的就是getInstance()方法获取对象实例,而在本例中因为加了synchronized关键字,即使在该实例已被创建出来,每个线程想要尝试获取Singleton实例都会被阻塞不能直接返回对象实例,需要等待前面的线程释放锁。
3.3懒汉式(线程安全--双重if判断)
class Singleton {
private static volatile Singleton instance;
private Singleton() {}
//提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题
//同时保证了效率, 推荐使用
public static Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
优点: 达到懒加载的效果,避免内存浪费,线程安全,对前面一种版本进行了改进,当该类已经被实例化后,后续线程在访问 当instance != null时直接返回即可,不会造成线程阻塞,实际开发中推荐使用这种方式
注意: 此处需加volatile关键字! 如不加volatile关键字,可能会出现,a线程已经创建了Singleton的对象实例,即将return的时候 (因为线程之间内存是独享的,instance在a线程中已经完成实例化,但还没有写到主物理),此时b线程在判断instance == null 会为true,重而又创建一个对象实例。而加了volatile关键字就可以保证变量的可见性,b线程判断就不会为空
4单例模式 -- 静态内部类
class Singleton {
private static volatile Singleton instance;
private Singleton() {}
//写一个静态内部类,该类中有一个Singleton类型的静态属性
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
//提供一个静态的公有方法,直接返回SingletonInstance.INSTANCE
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
特点 达到懒加载的效果,同式也保证了线程安全,实际开发中推荐使用这种方式。
原理 静态内部类,在Singleton类被装载时并不会立即实例化,而是在调用getInstance()方法时,去加载SingletonInstance类,从而完成Singleton的实例化, 因为类的静态属性,属性只会在第一次加载该类时初始化,即这里通过jvm类装载机制,保证线程的安全性。
5单例模式 -- 枚举
enum Singleton {
INSTANCE;
}
特点 这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过根据枚举的加载原理,它应该算饿汉式。
6.应用举例
6.1 java.lang.Runtime
通过源码我们可以发现,Runtime这个类就使用到了 单例模式饿汉式
7注意事项
-
单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
-
当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用构造方法
-
单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)