1.单例模式是什么?
单例模式,顾名思义就是在整个运行域,一个类只有一个实例对象.
1.1为什么需要单例模式
因为有的类型的实例对象的创建和销毁对资源来说消耗不大,但也有一些类型比较庞大和复杂,如果不断地去创建和销毁对象,且如果这些对象可以被复用,就会造成不必要的浪费.
2.如何实现单例模式?
要考虑的问题:
(1)是否线程安全
(2)是否懒加载
(3)是否可以反射破坏
2.1.懒汉模式
public class Singleton{
//创建一个私有构造,外部不能去创建
private Singleton(){};
//初始化对象为null,不创建实例
private static Singleton singleton = null;
//获取通过下面这个函数
public static Singleton getInstance(){
//如果这个对象是null,则创建实例
if(singleton==null){
singleton = new Singleton();
}
//最终返回这个对象
return singleton;
}
实例对象是第一次调用的时候才去创建,而不是饿汉单例那样程序一开始就自动创建和实例一个对象.懒汉模式,是当我需要的的时候我再去创建.
如果遇见对象构建开销是比较大的,如果项目刚一开始就被构建,但是之后就根本没用过,那就很浪费资源.所以用这种用时随取.
举个栗子:
[饿汉]:饿汉面对许多好吃的,饿的难受了,管他是好的坏的,管他有没有用,我先拿过来吃了再说.也就是说不管有用没用,先占着.
[懒汉]:天天就在床上躺着什么都不干,到了饭点了,才下床吃饭.需要的时候我再去,不需要的时候不动.
2.2.饿汉模式
上面也提到了饿汉与懒汉的区别.饿汉模式就是在一开始就创建对象实例.差别不是很大.
public class Singleton{
//创建一个私有构造,外部不能去创建
private Singleton(){};
//初始化对象为null,不创建实例
private static Singleton singleton = new Singleton();
//获取通过下面这个函数
public static Singleton getInstance(){
//最终返回这个对象
return singleton;
}
}
2.3.双检索模式
但是哦.上面所描述的懒汉模式是线程不安全的.当我们执行if(singleton==null)
的时候,如果有多个不同的线程进入,就会被实例化多次.
如果不想让多个线程同时进去,可以加一个synchronized.public static synchronized Singleton getInstance(){
.synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区.可以让线程互斥的访问代码,一次只能进入一个线程.
但是我们的目的是…只想在对象创建的时候让线程同步,目前这样是在每次获取对象时线程同步,这样无疑解决点没找对.
public class Singleton{
//创建一个私有构造,外部不能去创建
private Singleton(){};
//初始化对象为null,不创建实例
private static Singleton singleton ;
//我们让线程都进来
public static Singleton getInstance(){
//如果这个对象是null,则创建实例
if(singleton==null){
//如果实例对象没有创建,多个线程可以开始争抢对象锁
//抢到锁的线程开始创建实例对象
//之后的所有的线程执行到此处不用再创建,可以直接返回实例对象进行调用
synchronized (Singleton.class){
singleton = new Singleton();
}
}
//最终返回这个对象
return singleton;
}
}
但是还有这样一个情况存在,虽然在最后只有一个线程获取锁,但是其他线程在进入代码块后在if判断句处等待.抢到锁的线程执行完归还锁后,等待线程抢到锁又会创建一个实例对象.对象被多次创建了.
public class Singleton{
//创建一个私有构造,外部不能去创建
private Singleton(){};
//初始化对象为null,不创建实例
private static Singleton singleton ;
//我们让线程都进来
public static Singleton getInstance(){
//如果这个对象是null,则创建实例
if(singleton==null){
//如果实例对象没有创建,多个线程可以开始争抢对象锁
//抢到锁的线程开始创建实例对象
//之后的所有的线程执行到此处不用再创建,可以直接返回实例对象进行调用
synchronized (Singleton.class){
if(singleton==null){
singleton = new Singleton();
}
}
}
//最终返回这个对象
return singleton;
}
}
后续的某个等待线程抢到锁后,也会去判断实例对象是否已经创建,如果创建则不会再去创建
singleton = new Singleton().在指令层面这不是一个原子操作.
singleton = new Singleton()三段论:
(1)分配内存
(2)初始化对象
(3)对象指向内存地址
但是在真正执行的时候.Jvm虚拟机为了效率可能会对上面的指令进行重排,A线程执行完(1)直接执行(3).但是此时 singleton 还未被初始化…当下一个线程B执行到 if(singleton==null)时,返回false.直接跳过 singleton = new Singleton();返回 singleton对象,但是此时A线程还没有初始化完成,最后返回为null.
private volatile static Singleton singleton ;
加上volatile可以阻止作用在singleton 上的指令重排问题.
双检索模式就是if判断句里嵌套一个if判断句.