多线程十一 单例模式
本篇博文,将整理关于单例模式(就是让一个类从始至终,只能产生一个对象,而且spring管理的类也全部是单例模式的)与多线程摩擦出的火花
1 . 懒汉模式(存在线程安全性问题)
public class demo01 {
//要实现单例,肯定不能new对象,因此我们私有化构造函数
private demo01(){}
//定义一个属于本类的单例对象,每次返回的都是这个对象
public static demo01 instance = null;
//因为我们没有自己创造出来的对象了,故提供一个静态工厂方法,返回对象的实例
public static demo01 getInstance(){
if (instance==null){ //在多线程并发访问的情况下,是存在线程安全问题的
instance = new demo01();
return instance;
}
return instance;
}
}
- 懒汉模式---在使用的时候初始化对象
2 . 饿汉模式(简单粗暴,实现线程安全)----静态域
public class demo02 {
//要实现单例,肯定不能new对象,因此我们私有化构造函数
private demo02(){}
//定义一个属于本类的单例对象,每次返回的都是这个对象
private static demo02 instance = new demo02(); // 静态域
//因为我们没有自己创造出来的对象了,故提供一个静态工厂方法,返回对象的实例
public static demo02 getInstance(){
return instance;
}
}
- 饿汉模式---在类加载的时候初始化对象,
缺点:
1 . 如果在构造函数中有过多的其他耗时操作的话,对象的创建会很慢
2 . 而且对象创建出来了还不一定会马上使用,造成资源的浪费
使用饿汉模式相应的注意点 :
- 对象创建出来以后肯定会被使用
- 构造函数没有太多其他处理
3 . 饿汉模式(简单粗暴,实现线程安全)----静态块
public class demo05 {
//要实现单例,肯定不能new对象,因此我们私有化构造函数
private demo05(){}
//注意点, 下面两段代码是有先后顺序的,假如说颠倒顺序,那么已经初始化的实例也会被重置为null
public static demo05 instance = null;
static {
instance = new demo05();
}
//因为我们没有自己创造出来的对象了,故提供一个静态工厂方法,返回对象的实例
public static demo05 getInstance(){
return instance;
}
}
- 饿汉模式---在类加载的时候初始化对象,
缺点:
1 . 如果在构造函数中有过多的其他耗时操作的话,对象的创建会很慢
2 . 而且对象创建出来了还不一定会马上使用,造成资源的浪费
使用饿汉模式相应的注意点 :
- 对象创建出来以后肯定会被使用
- 构造函数没有太多其他处理
4. 懒汉模式----使用synchronized实现线程安全
public class demo03 {
//要实现单例,肯定不能new对象,因此我们私有化构造函数
private demo03(){}
//定义一个属于本类的单例对象,每次返回的都是这个对象
public static demo03 instance = null;
public static synchronized demo03 getInstance(){
if (instance==null){
instance = new demo03();
return instance;
}
return instance;
}
}
- 加上synchronized 在多线程并发访问的情况下,不再有线程安全问题,但是并不推荐,因为同一时间只有有一个线程进入此静态方法,因此效率低
5. 懒汉模式---- 双重同步锁单例模式+volatile 实现线程安全
public class demo04 {
//要实现单例,肯定不能new对象,因此我们私有化构造函数
private demo04(){}
//定义一个属于本类的单例对象,每次返回的都是这个对象
public static volatile demo04 instance = null;
// 双重同步锁单例模式
public static demo04 getInstance(){
if (instance==null){ // 检测 1
synchronized (demo04.class){ //锁
if (instance==null){ //检查 2
instance = new demo04();
}
}
return instance;
}
return instance;
}
}
为什么要加上volatile关键字?
这就要从CPU的指令说起, 当我们执行
new demo04();
分下面三步走
- memory = allocate(); //分配内存空间
- ctorInstance() //初始化对象
- instance = memory // 将对象的引用指向刚分配的内存空间
在单线程的情况下是不会发生任何线程安全问题的,但是! 多线程就会受到 指令重排序的影响, JVM和CPU优化--指令重排序可能出现下面的顺序
- memory = allocate(); //分配内存空间
- instance = memory // 将对象的引用指向刚分配的内存空间,
- ctorInstance() //初始化对象
这时候双重同步锁单例模式就会出现问题,比如AB两条线程 A运行到指令3却没有真正创建对象 , 然后B去判断instance此时不为空,拿到了instance,一旦调用就会出现问题
6. 使用枚举实现单例模式 --线程安全
public class demo06 {
private demo06(){}
public static demo06 getInstance(){
return Singleton.INSTANCE.getDemo06Instance();
}
//私有的枚举类
private enum Singleton{
INSTANCE;
private demo06 demo06Instance;
//JVM保证此构造方法绝对只会调用一次
Singleton(){
demo06Instance= new demo06(); //调用外部类私有的构造方法
}
public demo06 getDemo06Instance(){
return demo06Instance;
}
}
}
推荐使用这种方法
- 相对懒汉模式,绝对性的保证的安全问题
- 相对饿汉模式,当实例在使用的时候才开始初始化