在开发过程中会有许多常见的“场景”,针对这些场景,前辈们总结出了一些固定的套路。单例模式就是设计模式中的一种。单例模式能保证某个类在程序中只存在唯一一份实例,而不会创建出多个实例。
单例模式的实现
1.饿汉模式
static class Singleton{
private static Singleton instance=new Singleton();//static修饰的变量是类变量,一个类只有一份
public Singleton getInstance(){
return instance;//提供接口
}
private Singleton(){
;//把构造方法设为private,此时在类的外面,就无法继续new实例
}
2.懒汉模式
饿汉模式死在类加载阶段就创建的实例,创建实例的时机过早。而懒汉模式创建实例更迟,需要创建时才创建,效率更高。
static class SingletonLazy{
private static SingletonLazy instance=null;
public static SingletonLazy getInstance(){
if(instance==null){
instance=new SingletonLazy();//需要创建实例的时机,首次调用getInstance()才会触发,后续在调用就会立即返回
}
return instance;
}
private SingletonLazy(){
;
}
}
但是上诉代码在多线程环境下由于创建实例即涉及读,又涉及写操作会导致线程不安全。例如t2线程在进行load的时候,t1还没修改完,t2读到还是旧值,t2仍能进行new操作,就会导致创建多份实例。要想t2读到t1修改的值,就需要t2在load要在t1执行完new操作。
解决方案->加锁
static class SingletonLazy{
private static SingletonLazy instance=null;
public static SingletonLazy getInstance(){
synchronized (SingletonLazy.class) {
if (instance == null) {
instance = new SingletonLazy();//需要创建实例的时机,首次调用getInstance()才会触发,后续在调用就会立即返回
}
}
return instance;
}
private SingletonLazy(){
;
}
}
加锁解决了线程安全问题,但是加锁开销也不小,如果像上面代码,导致后续线程已经安全的时候仍然加锁,实际上只需要首次调用时候加锁即可。
static class SingletonLazy{
private static SingletonLazy instance=null;
public static SingletonLazy getInstance(){
if(instance==null) {//判断是否要加锁
synchronized (SingletonLazy.class) {
if (instance == null) {//判断是否要创建实例
instance = new SingletonLazy();//需要创建实例的时机,首次调用getInstance()才会触发,后续在调用就会立即返回
}
}
}
return instance;
}
private SingletonLazy(){
;
}
}
你以为上面代码已经没问题?其实还有线程不安全问题->指令重排序
new操作本质上分为3个步骤
1.申请内存,得到内存首地址
2.调用构造方法,来初始化实例
3.把内存首地址赋值给instance引用
在单线程中2,3是可以调换位置的,但是在多线程中,如果按照1,3,2执行,在t1线程执行1,3之后,执行2之前。t2线程调用getInstance就会认为Instance非空,直接返回instance,后面对instance进行解引用操作(使用里面的属性,方法)
解决方案->volatile修饰
static class SingletonLazy{
private volatile static SingletonLazy instance=null;
public static SingletonLazy getInstance(){
if(instance==null) {//判断是否要加锁
synchronized (SingletonLazy.class) {
if (instance == null) {//判断是否要创建实例
instance = new SingletonLazy();//需要创建实例的时机,首次调用getInstance()才会触发,后续在调用就会立即返回
}
}
}
return instance;
}
private SingletonLazy(){
;
}
}