目录
什么是单例
官方定义: 单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点——”通常我们可以让一个全局变量使得一个对象访问 ,但它不能防止你实例化多个对象 。 一个最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。[DP]“
众所周知,在java中 , 对象的实例最开始会被分配到堆区中的新生代区中 ,而在达到一定条件后,对象会被转移到老年代中。
而单例模式就是在对象第一次实例化的时候在堆区中开辟空间,并让栈区中的引用指向对象所在对堆区地址,而在后面的实例化中 , 都不会生成新的对象实例 ,更多的只是让其他栈区引用指向原先的对象实例地址——(对象实例自始至终只有一个,且如果该对象一直可达的话,后续会被分配到老年代中)。
使用场景
单例模式应该是开发种使用较多的一种设计模式,其具备多种使用场景。
一、windows任务管理器 , 每次我们尝试使用 Ctrl+Shift+Esc 的时候 , 可以打开多个任务管理器窗口嘛?
二、网站的全局计数器 , 为保证同步,需要在统一时间统计数据 , 采用单例模式,保证计数器全局唯一 , 从而达到同步技术的效果。
三、类似于第一条,对于一个窗口或组件,始终在同一时间只打开一个。要么打开、要么关闭。
四、开发中的Web配置文件,例如:Vue的main.js , SSM整合框架中的Pom等……
……
实现方法
单例模式实现分为 饿汉式 与 懒汉式
饿汉式(静态常量)
public class SingleTon {
private SingleTon(){}
private final static SingleTon Instance = new SingleTon();
public static SingleTon getInstance() {
return Instance;
}
}
Instance 静态变量指向类实例 ,意味着在类加载的时候,就会进行实例化 ,并指向该实例地址
这也是饿汉式的由来—— 类在加载的时候就实例化,而非用户自己去实例化。
饿汉式(静态代码块)
public class Singleton {
private Singleton(){}
private static Singleton instance = null;
static {
instance = new Singleton();
}
public static Singleton getInstance() {
return instance;
}
}
static {} 代码块类似于静态变量 , 在java中 , 类在被加载的时候 ,static代码块有且仅有一次被执行。
懒汉式 (内部类)
public class SingleTonTest2 {
// private static volatile SingleTonTest2 instance;
private SingleTonTest2() {}
public static class SingleTonInstance {
private static final SingleTonTest2 INSTANCE = new SingleTonTest2();
}
public static SingleTonTest2 getInstance(){
return SingleTonInstance.INSTANCE;
}
}
外部类在加载时并不会主动去加载内部类 , 需要用户主动去调用内部类的类方法、类属性或者实例化内部类!!!
懒汉式(双重检查锁)
public class SingleTonTest {
private SingleTonTest(){}
private static volatile SingleTonTest instace;
public static SingleTonTest getInstance() {
if(instace == null) {
synchronized (SingleTonTest.class) {
if(instace == null) {
instace = new SingleTonTest();
}
}
}
return instace;
}
}
第一个 if 判断 , 用来判断该类是否已经实例化 。(多线程下,会有线程安全问题)。
于是 , 在if 代码块中 添加 sync 重量级锁保证同步线程安全。注 : sync锁的是整个类,而不是实例 , 具体细节需要读者学习 java JUC相关知识 , 在这里就不一一详谈了。
至于在同步块中的 第二个 if 判断 , 是为了再次确实该类是否已经被初始化 , 从而决定是否实例化还是退出同步。注 : 在该同步块中 , 可能会出现指令重排序问题 ——在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序 , 这时就需要使用 volatile关键修饰 instance , 保证该属性的可见性与有序性 ,具体可参考 JAVA JUC 相关知识点。
饿汉式(枚举类)
enum Singleton {
INSTANCE;
dosomething...
}
最为简洁高效的方法 。
优点 : 可以保证线程安全,且不会被反射和序列化 破坏单列。
总结:
1. 实现方式中 , 饿汉式的两种方法均不会产生线程安全问题 。但除了枚举类实现,以上几种均可以通过反射和序列化破坏单例。
2. 为什么静态内部类线程安全?
答: JVM保证对于每一个类或接口C,都有一个唯一的初始化锁LC
与之对应;保证一个类的类构造器在多线程环境中被正确的加锁、同步。