目录
一、概述
什么是单例模式:在整个系统中,每个类仅有一个对象实例
应用场景:
- 业务系统中仅需要一个对象的时候,redis连接对象
- Spring IOC容器中的对象默认是单例的
单例模式的分类:
- 饿汉式:在加载类的时候就创建对象;
- 懒汉式:懒加载模式,在需要获取对象的时候才会创建对象
单例模式实现步骤:
- 构造方法私有化
- 私有的实例对象
- 对外暴露获取实例对象的方法
二、饿汉式
饿汉式实现简单,而且不会涉及线程安全问题;对象在类加载的时候就会创建,但是缺点在于如果对象不被使用,就会一直占据系统资源
public class Singleton {
// 饿汉式
private static Singleton singleton = new Singleton();
private Singleton() {
System.out.println("生成一个实例");
}
public static Singleton getInstance() {
return singleton;
}
}
调用:
public static void main(String[] args) {
new Singleton(); // 编译无法通过
Singleton tareget = Singleton.getInstance();
}
三、懒汉式
实现方面,就是什么时候要获取实例对象,什么时候进行创建; 注意:懒汉式有线程安全问题
3.1 基本实现:
/**
* 懒汉式实现
*/
public class SingletonLazy {
// 1.私有成员变量,但不直接创建
private SingletonLazy singletonLazy;
// 2.构造函数私有化
private SingletonLazy() {
System.out.println("创建一个对象");
}
// 3.对外暴露获取对象的方法
public SingletonLazy getInstance() {
if (singletonLazy == null) {
singletonLazy = new SingletonLazy();
}
return singletonLazy;
}
}
这个实现中,可能存在同时多个线程调用方法,导致最终创建的对象不是同一个,可以使用synchronized解决,但是考虑如果对整个方法上锁,整个执行效率会产生下降,因此应该考虑局部上锁。
而且,这个锁一定上在 if 中,否则和对整个方法上锁没什么区别
// 3.对外暴露获取对象的方法
public SingletonLazy getInstance() {
if (singletonLazy == null) {
// 考虑这个部分是否有问题??
synchronized (SingletonLazy.class) {
singletonLazy = new SingletonLazy();
}
}
return singletonLazy;
}
看注释的部分,可能存在一个场景:两个线程A, B同时进入 if 中,但是A先获取锁并创建对象,而B在获取锁之后又重新创建了对象,对象发生了变化;
因此,存在一次锁内检查:
// 3.对外暴露获取对象的方法
public SingletonLazy getInstance() {
if (singletonLazy == null) {
synchronized (SingletonLazy.class) {
if (singletonLazy == null) {
singletonLazy = new SingletonLazy();
}
}
}
return singletonLazy;
}
结论:双重检测锁定
3.2 指令重排
考虑对象的创建过程:
- 分配空间给对象
- 初始化对象
- 设置对象的内存地址,此时instance != null
但是在JVM中可能由于优化的原因导致创建对象的指令发生混乱:如 1->2-> 3 变为 1 -> 3 -> 2
产生的现象:
当第一个线程拿到锁并且进入到第二个if方法后, 先分配对象内存空间, 然后再instance指向刚刚分配的内存地址, instance 已经不等于null, 但此时instance还没有初始化完成。如果这个时候又有一个线程来调用getInstance方法, 在第一个if的判断结果就为false, 于是直接返回还没有初始化完成的instance, 那么就很有可能产生异常。
而使用 volatile 加载实例对象上,可是保证 变量线程间可见以及禁止指令重排
懒汉式完整实现:
public class SingletonLazy {
// 1.私有成员变量,但不直接创建
private volatile SingletonLazy singletonLazy;
// 2.构造函数私有化
private SingletonLazy() {
System.out.println("创建一个对象");
}
// 3.对外暴露获取对象的方法
public SingletonLazy getInstance() {
if (singletonLazy == null) {
synchronized (SingletonLazy.class) {
if (singletonLazy == null) {
singletonLazy = new SingletonLazy();
}
}
}
return singletonLazy;
}
}
2022/04/19 两种新见到的创建方式
四、懒汉式的其他实现
4.1 静态内部类
public class Demo04 {
private Demo04() {}
public static Demo04 getInstance() {
return new SingletonHelper().INSTANCE;
}
public static class SingletonHelper {
private static final Demo04 INSTANCE = new Demo04();
}
public static void main(String[] args) {
// Demo04 instance1 = Demo04.getInstance();
// Demo04 instance2 = Demo04.getInstance();
// System.out.println(instance1.equals(instance2)); // true
System.out.println("----------多线程测试-----------");
for (int i = 0; i < 100; i++) {
new Thread(() -> System.out.println(getInstance().hashCode())).start();
}
}
}
对于Demo04,由于类加载过程只进行一次,因此保证了全局唯一;并且对于静态内部类,只有在调用的时候才会进行类加载,满足了懒加载的方式
运行结果:所有获取的对象的HashCode都是相同的