本篇博文,将整理关于单例模式(就是让一个类从始至终,只能产生一个对象,而且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; } } }
推荐使用这种方法
- 相对懒汉模式,绝对性的保证的安全问题
- 相对饿汉模式,当实例在使用的时候才开始初始化