欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

单例模式中懒汉模式的非线程安全问题的解决方法

程序员文章站 2022-05-01 23:30:32
...
非原创,转自https://blog.csdn.net/noaman_wgs/article/details/53258710

单例模式中懒汉模式的非线程安全问题的解决方法

   单例模式中有饿汉模式和懒汉模式两种。饿汉模式也叫立即加载 ,即在get之前就已经创建实例instance;
  1. package singleton1;
  2. //饿汉模式(立即加载)
  3. public class Singleton {
  4. private static Singleton instance = new Singleton();
  5. private Singleton(){}
  6. public static Singleton getInstance(){
  7. return instance;
  8. }
  9. }

而懒汉模式也叫延迟加载,即在get时才会被创建实例。懒汉模式在多线程的环境中就会出现多实例的情况,与单例模式相背离。如下代码可证:
  1. package singleton2;
  2. //懒汉模式
  3. public class Singleton {
  4. private static Singleton instance = null;
  5. private Singleton(){}
  6. public static Singleton getInstance(){
  7. try {
  8. if(instance==null){
  9. Thread.sleep(1000);//模拟延迟加载
  10. instance=new Singleton();
  11. }
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. return instance;
  16. }
  17. }
创建线程:
  1. package singleton2;
  2. public class ThreadA extends Thread{
  3. @Override
  4. public void run(){
  5. System.out.println(Singleton.getInstance().hashCode());//根据实例对象哈希值是否相同来判断对象是否相同
  6. }
  7. }
创建运行类:
  1. package singleton2;
  2. public class Run {
  3. public static void main(String[] args) {
  4. ThreadA t1=new ThreadA();
  5. ThreadA t2=new ThreadA();
  6. ThreadA t3=new ThreadA();
  7. t1.start();
  8. t2.start();
  9. t3.start();
  10. }
  11. }
运行结果:
  1. 1321522194
  2. 2134502363
  3. 866891806
哈希值不同,所以对象不同,即不是单例模式。如何解决这个问题呢,下面提供了几种方法:


一、方法上声明synchronized关键字(效率低)

  1. package singleton2;
  2. //懒汉模式
  3. public class Singleton {
  4. private static Singleton instance = null;
  5. private Singleton(){}
  6. //1 方法上声明synchronized关键字
  7. synchronized public static Singleton getInstance(){
  8. try {
  9. if(instance==null){
  10. Thread.sleep(1000);//模拟延迟加载
  11. instance=new Singleton();
  12. }
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. return instance;
  17. }
  18. }

运行结果如下:
  1. 1876189785
  2. 1876189785
  3. 1876189785
哈希值相同,是单例模式的实现。但这种加了synchronized关键字后,多线程进行操作时只能等一个线程处理完,下一个线程才能进入同步方法中,效率低下。

二、同步代码块(效率低)

  1. package singleton3;
  2. //懒汉模式
  3. public class Singleton {
  4. private static Singleton instance = null;
  5. private Singleton(){}
  6. public static Singleton getInstance(){
  7. try {
  8. // 2 使用同步代码块,但效率低
  9. synchronized(Singleton.class){
  10. if(instance==null){
  11. Thread.sleep(1000);//模拟延迟加载
  12. instance=new Singleton();
  13. }
  14. }
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. return instance;
  19. }
  20. }

运行结果:
  1. 1619327594
  2. 1619327594
  3. 1619327594
       使用同步代码块方式也能解决上述问题,虽然不需要判断实例是否为null,但同样的也会带来效率低的毛病。

三、使用DCL(Double-Check Locking)双检查锁机制---(推荐

  1. public class Singleton {
  2. private static volatile Singleton instance = null;
  3. private Singleton(){}
  4. public static Singleton getInstance(){
  5. if(instance == null){
  6. synchronized(Singleton.class){
  7. if(instance == null){
  8. instance = new Singleton();
  9. }
  10. }
  11. }
  12. return instance;
  13. }
  14. }

这里为什么要用volatile修饰instance?
原因:在于instance = new Singleton()的时候,在内存中实际上是分3步执行的:
1)分配对象的内存空间:memory = allocate();
2)初始化对象:ctorInstance(memory);
3)指向分配的地址:instance =memory
多线程在执行的时候,2 3可能发生重排序。即有可能线程A执行到第3步的时候,读取到instance不为null,就返回。实际上此时还未执行第二部即未初始化。
加上volatile就可以避免2 3步重排序来保证线程安全。


四、使用静态内部类来实现单例模式

  1. package singleton6staticinnerclass;
  2. public class Singleton {
  3. //静态内部类进行初始化
  4. private static class SingletonHandler{
  5. private static Singleton instance = new Singleton();
  6. }
  7. private Singleton(){}
  8. public static Singleton getInstance(){
  9. return SingletonHandler.instance;
  10. }
  11. }

五、static代码块来实现单例模式

  1. package singleton7static;
  2. public class Singleton {
  3. private static Singleton instance = null;
  4. private Singleton(){}
  5. //静态代码块
  6. static{
  7. instance=new Singleton();
  8. }
  9. public static Singleton getInstance(){
  10. return instance;
  11. }
  12. }

还有序列化和反序列化的单例模式来实现,或者使用枚举类来实现,在此就不一一介绍了,有兴趣可以百度下。