目录
单例模式代码为面试经典、高频问题!!
单例模式
使某个类,只有唯一一个对象.通过编码技巧,让编译器强行检查
饿汉模式
(类加载的时候就创建对象)
public class Singleton {
//类加载的时候就创建
private static Singleton instance=new Singleton();
//后续需要获取这个类的实例,都通过getInstance方法来获取
public static Singleton getInstance(){
return instance;
}
//把构造方法设为私有,外面的代码就无法new出这个类的对象了
private Singleton(){};
}
懒汉模式
(在第一次使用的时候创建对象)
public class SingleletonLazy {
private static SingleletonLazy instance=null;
public static SingleletonLazy getInstance(){
if(instance==null){
instance=new SingleletonLazy();
}
return instance;
}
private SingleletonLazy(){}
}
饿汉模式中,只是读取对象,不会有线程安全问题
懒汉模式中,又读又写对象,会有线程安全问题
如果两个线程中穿插执行了,就会new出多个实例
解决方式:
加锁 将if和new合并成一个整体
public class SingleletonLazy {
private static SingleletonLazy instance=null;
public static SingleletonLazy getInstance(){
synchronized (SingleletonLazy.class){
if(instance==null){
instance=new SingleletonLazy();
}
return instance;
}
}
private SingleletonLazy(){}
}
实际上加锁是一个开销很大的操作,可能会涉及锁冲突,一冲突就会涉及到阻塞等待
该如何解决呢?
在加锁语句外层,加一个判断条件,判定一下这里是否需要加锁
如果对象已经有了,线程就安全了,此时可以不加锁了
如果对象还没有,存在线程不安全的风险,就需要加锁
class SingleletonLazy {
private static volatile SingleletonLazy instance=null;
public static SingleletonLazy getInstance(){
if(instance==null){
synchronized (SingleletonLazy.class){
if(instance==null){
instance=new SingleletonLazy();
}
}
}
return instance;
}
private SingleletonLazy(){}
}
指令重排序
编译器为了执行效率,可能会调整执行顺序,前提是保持逻辑不变
单线程中无所谓,而多线程中可能会出现误判
上文中懒汉模式的代码:
public class SingleletonLazy {
private static SingleletonLazy instance=null;
public static SingleletonLazy getInstance(){
if(instance==null){
synchronized (SingleletonLazy.class){
if(instance==null){
instance=new SingleletonLazy();
}
}
}
return instance;
}
private SingleletonLazy(){}
}
new操作,可能出触发指令重排序
new操作分为三步:
①申请内存空间
②在内存空间上构造对象(构造方法)
③把内存的地址,赋值给instance引用
(会按照①②③、①③②的顺序来执行)
假如按照①③②,当t1执行完①③后,instance已经是一个非空的对象了,但是此时指向的是一个没有初始化的非法对象
而如果此时t2开始执行了,判断instance==null不成立,直接return instance,进一步t2线程的代码就可能会访问instance的属性和方法了
上述问题,解决方法——volatile
让volatile修饰instance,在instance修改过程中就不会出现指令重排序现象了
所以,我们得出
单例模式的最终版本
(还有反射、序列化、反序列化的问题,这里不做过多讨论)
注意①②③注意点