zoukankan      html  css  js  c++  java
  • 单例设计模式(这一篇足够了)

      单例模式真是一个老掉牙的问题了,不过我今天是要说些里面更深点的知识,闲话少说,直接来代码

      1、饿汉式

       相信这种写法大家都知道,一开始接触单例的时候,大家应该都是用的这种方法:

    package com.hd.single;
    
    public class Singleton {
    
        private Singleton(){}
        private static Singleton instance = new Singleton();
    
        public static Singleton getInstance(){
            return instance;
        }
    }

      这种方式优点就是线程安全, 缺点也很明显,就是类加载的时候,就已实例化该对象了,后面有可能用不到这个实例对象,这样就会造成空间浪费。因此就有了懒加载方式。

      2、懒汉式

      1)懒汉式L1

    package com.hd.single;
    
    public class Singleton2 {
    
        private Singleton2(){}
        private static Singleton2 instance;
    
        public static Singleton2 getInstance(){
            if(instance == null)        //1
                instance = new Singleton2();  //2
            return instance;
        }
    
    }

      这种懒汉式的优点和缺点也很明显,优点是按需加载,节省空间, 缺点是线程不安全。简单说就是,有可能线程A执行到“1”处时,阻塞住了,线程B抢到CPU,进来执行并实例化对象,然后线程A醒来后,继续往下执行,这样线程A和B取到的就是不同的对象。因此,又有了线程安全的版本。

    package com.hd.single;
    
    public class Singleton2 {
    
        private Singleton2(){}
        private static Singleton2 instance;
    
        public static synchronized Singleton2 getInstance(){
            if(instance == null)
                instance = new Singleton2();
            return instance;
        }
    }

      但是加了synchronized 之后会造成线程阻塞,影响性能。于是又提出了双检锁的方式

     1 package com.hd.single;
     2 
     3 public class Singleton2 {
     4 
     5     private Singleton2(){}
     6     private static Singleton2 instance;
     7 
     8     public static Singleton2 getInstance(){
     9         if(instance == null){
    10             synchronized (Singleton2.class){
    11                 if(instance == null){
    12                     instance = new Singleton2();
    13                 }
    14             }
    15         }
    16         return instance;
    17     }
    18 }

      看似双检锁的方式很完美,既解决了线程安全的问题,又兼顾了性能问题: 线程先判断instance变量是否为空,如果不为空,则直接返回。否则进入同步块去实例化对象。但事实这是一个错误的优化!

      重点就是第12行代码(instance = new Singleton2();), 它创建了一个对象。这一行代码可以分解为如下的3行代码:

    memory = allocate();         //1:分配对象的内存空间
    ctorInstance(memory);        //2:初始化对象
    instance = memory;           //3:设置instance指向刚分配的内存地址

      上面2和3这两步在执行的时候,有可能会被重排序(具体指令重排序知识点,可以去网上搜索相关内容,一大堆,我就不详细说了。本质就是jvm为了优化而使用的),2和3重排序之后的执行时序如下:

    memory = allocate();         //1:分配对象的内存空间
    instance = memory;           //3:设置instance指向刚分配的内存地址
                               //注意此时对象还没有被初始化
    ctorInstance(memory);        //2:初始化对象
                                        

      因此如果有线程A执行到3时,此时instance变量确实不为空,然后线程B判断instance不为空后返回,那么这是时程B 取到的就是一个空的对象。显示这样是有问题的,因此为了防止出现这个问题,我们可以使用volatile变量,来禁止指令重排序。

      2)懒汉式 L2(基于volatile的解决方案)

    package com.hd.single;
    
    public class Singleton {
    
        private Singleton(){}
        private volatile static Singleton instance;
    
        public static Singleton getInstance(){
            if(instance == null){
                synchronized (Singleton.class){
                    if(instance == null){
                        instance = new Singleton3();
                    }
                }
            }
            return instance;
        }
    }

      我们除了通过volatile的方式来禁止指令重排序,还可以提供另外一种思路:允许2和3重排序,但不允许其它线程“看到”这个重排序。 前面正是因为线程B看到了重排序,发现instance变量不为空,所以才造成其取到空的对象。

      3)懒汉式L3(基于静态内部类的方案)

    package com.hd.single;
    public class LazySingleton2 {
        private LazySingleton2() {
        }
        static class SingletonHolder {
            private static final LazySingleton2 instance = new LazySingleton2();
        }
        public static LazySingleton2 getInstance() {
            return SingletonHolder.instance;
        }
    }

      因为 在加载外部类时,其内部类不会同时被加载。只有调用 getInstance方法的时候,内部类才会去被加载,且只加载一次,不存在并发问题,因此是线程安全的。

      另外,在getInstance()方法中没有使用synchronized关键字,因此没有造成多余的性能损耗。

      本文给出了多个版本的单例模式,供我们在项目中使用。一般用L2,L3就基本够用。

  • 相关阅读:
    ES 分词器简单应用
    ElasticSearch 通过 Kibana 与 ElasticSearch-head 完成增删改查
    linux 通过docker安装 elasticsearch-head
    The container name "/nacos" is already in use by container
    mysql 用户及用户权限管理命令总结-用户添加及添加权限
    docker 安装 ElasticSearch 和 Kibana 及ik 中文分词器
    docker 安装 nacos
    mysql 主从状态查询及恢复
    那些看似牛逼的「快速阅读法」为什么全是错的!?
    新手VS高手,高手是怎么读书的?
  • 原文地址:https://www.cnblogs.com/xiexin2015/p/9043429.html
Copyright © 2011-2022 走看看