• 写一个安全的Java单例


      单例模式可能是我们平常工作中最常用的一种设计模式了。单例模式解决的问题也很常见,即如何创建一个唯一的对象。但想安全的创建它其实并不容易,还需要一些思考和对JVM的了解。

      1.首先,课本上告诉我,单例这么写

      

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

      这段代码最大的问题就是它并不是线程安全的。即在多线程情况下可能new 出多个对象。试想有两个线程同时执行到了第9行,由于没有锁机制,那么两个线程都会进入,就会new出多个对象。

     1 public static CountDownLatch latch = new CountDownLatch(2);
     2 
     3     public static void main(String[] args) {
     4         for (int i = 0; i < 2; i++) {
     5             new Thread(new Runnable() {
     6 
     7                 @Override
     8                 public void run() {
     9                     latch.countDown();
    10                     try {
    11                         latch.await();
    12                     } catch (InterruptedException e) {
    13                         // TODO Auto-generated catch block
    14                         e.printStackTrace();
    15                     }
    16                     System.out.println(Singleton.getInstance());
    17                 }
    18             }).start();
    19         }
    20     }

      我用上面代码来演示 第一种单例写法的结果。最后会调用Object的toString方法来打印Singleton对象的hashcode

      结果如下

      

    第一次结果:
    com.deng.pp.Singleton@33bbe97
    com.deng.pp.Singleton@11989480
    
    第二次结果:
    com.deng.pp.Singleton@1c0956b9
    com.deng.pp.Singleton@5e70125b
    
    第三次结果:
    com.deng.pp.Singleton@5e70125b
    com.deng.pp.Singleton@1c0956b9
    
    第四次结果:
    com.deng.pp.Singleton@1c0956b9
    com.deng.pp.Singleton@1c0956b9
    
    第五次结果:
    com.deng.pp.Singleton@1c0956b9
    com.deng.pp.Singleton@1c0956b9

      可以看出,单例代码1确实会存在new 出多个对象的情况。

      将单例代码1的getInstance方法 改成如下,对getInstance方法加synchronized 关键字

      

    public static synchronized Singleton getInstance() {
      if (instance == null) {
        instance = new Singleton();
      }
      return instance;
    }

      下面是5次测试结果

      

    第一次:
    com.deng.pp.Singleton@33bbe97
    com.deng.pp.Singleton@33bbe97
    第二次
    com.deng.pp.Singleton@12ceba90
    com.deng.pp.Singleton@12ceba90
    第三次
    com.deng.pp.Singleton@5e70125b
    com.deng.pp.Singleton@5e70125b
    第四次
    com.deng.pp.Singleton@1c0956b9
    com.deng.pp.Singleton@1c0956b9
    第五次
    com.deng.pp.Singleton@1c0956b9
    com.deng.pp.Singleton@1c0956b9

      可以确定synchronized确实起了作用。这么做是可以work的,执行结果也没有什么错误。但它有一个最大问题是效率问题。每个线程调用getInstance方法是都要去判断是否有其他线程在执行这个方法,即使instance已经存在也需要去判断是否有线程在方法里面。如果有,就要在外边等。而实际上只需要在new 对象之前等就可以了。根据这个就有了下面的方法:双重检查锁

      

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

      来分析一下,假设两个线程到达getInstance方法,线程1先获得了锁,进入初始化方法。线程2因未获得锁在外边等待,线程1出去后,线程2进入同步块,instance不是null,return,完美。

      但是结果可能并不是这样,因为 对象的new操作并不是 原子 的。JVM new 对象的过程大致如下

      1.在堆上分配一块内存空间  2.实例化类放入1分配的内存空间 3.把引用赋给instance

      如果按照123的顺序,上面那段代码就没有问题。但JVM中存在指令重排,即编译器对代码进行优化,改变不相互依赖的代码的执行顺序。上述1,2,3中第三步并不依赖于第二步,即可能存在132这样的顺序。

      那么这种顺序下,线程1执行到3。线程2进入方法,此时由于instance已被赋值,所以不为null。直接return,此时return的对象是不正确的,因为线程1还没有将对象完全初始化完。

      (很抱歉,在我的环境下并没有重现这种问题,如果有其他的可以测试出这种问题的方法,望不吝赐教。)

      解决办法是将instance字段改成

        private volatile static Singleton instance;

      volatile 关键字会禁止指令重排序,从而保证了单例正确性。

      下面的方法也可以实现单例,因为SINGLETON为static的所以在类加载时就会初始化,final保证了只会赋一遍值。项目较小时可以用,很方便,类很多的时候如果都上来就加载可能就很浪费资源了。

    public static final Singleton SINGLETON = new Singleton();

      Effective Java作者推荐了一种更好更安全的写法。

    public class Singleton {
    
        private Singleton() {
        }
    
        public enum Instance{
            INSTANCE;
            
            private Singleton singleton;
            
            Instance() {
                singleton = new Singleton();
            }
            
            public Singleton getInstance(){
                return singleton;
            }
        }
    }

      最近才开始写博客,才疏学浅,如文中有任何错误请留言交流。谢谢~

  • 相关阅读:
    python学习永久存储和异常处理
    python学习os文件系统模块
    python学习文件
    python学习集合
    python学习字典
    python学习递归
    python学习函数
    python学习序列
    js加入收藏
    判断dataset和datareader中是否存在某列
  • 原文地址:https://www.cnblogs.com/xushy/p/8446736.html
Copyright © 2020-2023  润新知