单例模式
干啥用的
用来替换全局变量,全局变量的缺点在程序一开始就会被创建,如果这个对象非常消耗资源,而程序在这次执行过程中又一直没有用到它就形成了浪费
利用单例模式可以解决这个问题
保证一个类有且仅有一个实例,并提供一个访问它的全局访问点
如何实现
1.要自己控制生成实例,所以需要使用private来声明构造函数,这样就防止了调用方创建这个对象的实例
2.单例模式没有公开的构造器 一句话我命由我不由天
3.如何调用这个私有的构造函数呢?static修饰的方法是类方法,不需要实例化这个类也可以调用,所以需要一个static的getInstance方法
常见的实现
懒汉模式
实现简单,按照上面的思路瞬间就写出来了,但是这个真的保证了单例吗,在单线程的时候保证了,但在多线程的时候就不能保证了,
因为可能会有两个线程同时执行getInstance方法这时就可能变成多例了
优点
实现简单+懒加载
缺点
线程不安全
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉升级版线程安全模式
既然有多线程不安全第一个想法就是同步代码块咯
哪里不安全我就同步哪里
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点
线程安全+懒加载
缺点
同步代码块这么做合理吗,其实只有第一次执行的时候才会发生不安全问题,有没有优化空间呢
饿汉模式
刚刚我们都是从代码层面保证了单例的,换个角度可不可以利用jvm的加载原理帮我们解决这个问题
static修饰的变量在jvm进行类加载时就会被初始化,类中的静态成员会随着类的加载而加载。而类加载机制保证每一个类只会被加载一次,所以就可以保证单例咯
public class Singleton {
private static Singleton instance=new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
优点
线程安全
缺点
不是懒加载,在类加载时就初始化了所以会存在资源浪费问题
双重检查加锁
先处理线程问题,看看实例是否已经创建了,如果没创建才开启同步模式,这样就减少了同步的次数只会在第一次调用getInstance进行同步
public class Singleton {
private static Singleton instance=new Singleton();
public class Singleton {
private volatile static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if(instance==null){//第一次检查是不是有实例,如果有直接返回
synchronized(Singleton.class){//如果没有就进入同步代码块
if(instance==null){//进入同步代码块再检查一次
instance=new Singleton();
}
}
}
return instance;
}
}
为什么要检测两次
第一次是针对所有情况的
第二次是针对第一次调用这个getInstace是否要实例化Singleton的
可能一开始有多个线程经过了第一次检测进入了同步块如果内部没有再次检测就会生成多个实例
为什么要加volatile,因为new操作可能存在指令重排序问题
new操作可以拆为如下三步
-
分配内存空间
-
初始化对象
-
将对象指向刚分配的内存空间
编译器为了优化性能可能会重排序导致第三步先于第二步执行,这是别的线程进入访问的instance就是一个未初始化的对象就导致错误
优点
高性能
缺点
实现复杂要考虑的地方比较多