定义
单例模式(Singleton):就是采用一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
单例模式的八种设计方法
静态常量饿汉式
饿汉式:在类加载时,就直接创建出类的实例化对象。就像饿怕了一样,不论需不需要都要先把食物准备好。
步骤
- 设置构造函数权限为private
- 在类内部创建一个private final static对象实例
- 创建一个public的getInstance方法获取实例
1 | public class Singleton { |
优点:
- 写法简单,代码容易理解
- 在类加载的时候完成实例化,让JVM保证线程的同步问题
缺点:
- 在类加载的时候完成实例化,如果没有用到这个实例,则会导致内存浪费
在实际的开发中,如果内存占用较小,可以使用这种方法
静态代码块饿汉式
和静态常量饿汉式相似,只不过将静态变量的赋值放在静态代码块中
1 | public class Singleton { |
优缺点和静态常量饿汉式相同。
在实际的开发中,可以使用这种方法
线程不安全懒汉式
懒汉式:不用提前创建对象,等到需要的时候才去创建。就像很懒的人一样,等到饿了才会去寻找食物。
步骤
- 设置构造函数权限为private
- 创建一个public的getInstance方法获取实例,当对象为空,说明没有创建过,此时进行创建,否则该对象已经创建过,直接返回即可
1 | public class Singleton { |
优点:
- 写法简单,代码容易理解
- 达到了懒加载的效果,不会导致内存浪费
缺点:
- 只能在单线程下使用,在多线程下,如果一个线程通过if(singleton == null)的判断,另一个线程也通过了这个语句,此时就会产生多个实例。
在实际的开发中,不能使用这种方法
线程安全懒汉式
使用同步方法,保证同时只允许一个线程进入getInstance方法。
1 | public class Singleton { |
优点:
- 解决了懒汉式线程不安全的问题
缺点:
- 效率较低,每次执行getInstance方法都要进行加锁。
在实际的开发中,不推荐使用这种方法
同步代码块懒汉式
在if(instance == null)中添加同步代码块
1 | public class Singleton { |
和线程不安全懒汉式一样,如果一个线程进入了if(singleton == null)的语句中,另一个线程也通过了这个语句,此时就会产生多个实例,并没有起到线程安全的作用。
在实际的开发中,不能使用这种方法
双重检查懒汉式
步骤
- 使用private static volatile 修饰静态Singleton对象
- 在if(instance == null)代码块中添加同步代码块,并在同步代码块中再次检查if(instance == null)
第一个判断的目的:防止每次调用都会加锁,只有为null的时候才要创建对象,此时才应该加锁
第二个判断的目的:防止多个线程都在等锁,第一个线程创建完毕释放锁后,后面的线程拿到锁继续创建对象
这里出现了volatile修饰符,小伙伴们可能会觉得有些陌生。volatile的目的是保证Java代码的可见性和有序性。
- 可见性:当用volatile修饰变量时,JVM会把该线程本地内存中的变量强制刷新到主内存中,并且这个操作会使其他线程中的变量缓存无效。
- 有序性:编译器为了优化程序性能,往往会对指令序列进行重排,使用volatile可以禁止指令重排。重排的规则是
- 不会对存在数据依赖关系的操作重排,如a = 0; b = a;因为b依赖于a,因此不会产生重排,如果a = 0; b = 1;此时可能会产生重排。
- 不会对运行结果产生改变,如a = 0; b = a; a = 2;因为b依赖于a,因此前两句话的顺序是不能重排的,但是第三句话会不会插入到前两句话中间呢?答案也是否定的,因为这样会导致代码运行结果被改变。
这里为什么要加入volatile修饰符呢?是因为instance = new Singleton();这一行可以分成三个步骤
- 申请内存
- 初始化对象
- 将初始化的对象分配到内存中
如果此时产生了重排序,可能会申请完内存后,还没有完成初始化就分配内存了。此时instance不为null,如果其他线程获取了这个instance,并去使用,就会导致使用一个未初始化完毕的对象,可能产生问题。
1 | public class Singleton { |
优点:
- 线程安全
- 需要时再加载,不会产生内存浪费
- 效率较高
在实际的开发中,推荐使用这种方法
静态内部类懒汉式
静态内部类的特点:
- 当外部类被加载时,静态内部类不会被加载
- 当静态内部类加载时,仅会加载一次,且线程是安全的
步骤
- 创建静态内部类,在静态内部类中创建Singleton实例
- 在外部类中提供一个方法,返回内部类创建的Singleton实例
1 | public class Singleton { |
优点:
- 采用类加载机制保证初始化实例时只有一个线程,保证线程安全
- 静态内部类在Singleton被加载时不会立即实例化,不会产生内存浪费
在实际的开发中,推荐使用这种方法
枚举饿汉式
枚举方式有些奇怪,不符合我们的正常认知,但仍然是满足需要的一种方式
1 | enum Singleton { |
优点:
- 写法简单,代码容易理解
- 在枚举类加载时完成实例化,避免线程同步问题
- 最安全,能够防止反射、反序列化重写创建对象
缺点:
- 因为枚举的成员都是静态的,因此是饿汉式加载机制,可能会导致内存浪费
在实际的开发中,推荐使用这种方法
总结
单例模式保证了系统内存中该类仅存在一个对象,节省系统资源,避免频繁创建和销毁对象,提高系统性能。是最简单也是经常使用的一种设计模式,虽然这里介绍了八种方法创建单例模式,但是小伙伴们要仔细阅读其优缺点,可能有一些方法无法真正做到单例或者是实际开发中不推荐使用的方法,这是我们需要牢记的。