单例模式专题
单例模式保证了系统内存中该类只有一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
使用场景:需要频繁创建和销毁的对象、创建对象耗时较多、耗费资源过多但又经常用到的对象、工具类对象、频繁访问数据库和文件的对象(比如数据源或session工厂)
饿汉式
优点:在类装载的时候就完成了实例化,避免了线程同步的问题
缺点:在类装载的时候就完成了实例化,没有达到Lazy Loading的效果,如果至始至终没有用到这个实例,那么会造成内存的浪费
静态常量方方式创建
1、构造器私有化(防止通过 new 创建一个对象实例)
2、类的内部创建对象
3、向外暴露一个静态的公共方法,通过该方法返回该类的对象实例(getInstance)
静态代码块方式创建
与上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的,可能造成内存浪费
枚举方式创建
enum类的本质其实是一个final修饰的类,因为枚举的性质,INSTANCE 又是由final static修饰的,所以我们用enum来创建单例。而JVM来管理这个实例,也保证了线程安全,推荐使用
反射破坏单例
1 | public static void main(String[] args) throws Exception { |
为了预防这种情况,可以在构造方法内进行判断
反序列化破坏单例
当实现了Serializable接口时,可以通过反序列化的方式破坏单例
1 | public static void main(String[] args) throws Exception { |
为了预防这种情况,可以重写readResolve方法
unsave破坏单例
1 | public static void main(String[] args) throws Exception { |
懒汉式
线程不安全方式创建
起到了 Lazy Loading 的效果,但是只能在单线程下使用
如果在多线程下,一个线程进入了 if (singleton == null)判断语句块,还没来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式
注意:在实际开发中,不要使用这种方式
同步方法的方式创建
解决了线程安全问题。但是效率太低,每个线程在想获得类的实例时候,执行getInstance()
方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接 return 就行了。方法进行同步效率太低
同步代码块的方式创建(x)
并不能起到线程同步的作用,实际开发中,不能使用这种方式
双检锁的方式创建
进行了两次 if (singleton == null)检查,保证线程安全。而且实例化代码只用执行一次,后面再次访问时,判断 singleton == null,直接 return 实例化对象,也避免的反复进行方法同步
线程安全;延迟加载;效率较高
volatile的作用:
INSTANCE = new Singleton()
不是原子的,分成 3 步:创建对象、调用构造、给静态变量赋值,其中后两步可能被指令重排序优化,变成先赋值、再调用构造
如果线程1 先执行了赋值,线程2 执行到第一个 INSTANCE == null
时发现 INSTANCE 已经不为 null,此时就会返回一个未完全构造的对象
内部类方式创建
根据内部类的特性,仅有在调用时才会加载类
1 | public class Singleton implements Serializable { |
jdk中的体现
在jdk中应用,java.lang.Runtime
就利用了单例模式
每一个java应用程序都有一个Runtime实例。Runtime的单例模式是采用饿汉模式创建的,当你加载这个类文件时,这个实例就已经存在
1 |