zoukankan      html  css  js  c++  java
  • 设计模式之单例模式

    一、概念

    JVM中,单例对象只有一个实例存在。

    二、饿汉式实现

    public class Singleton {
        private static Singleton instance = new Singleton();
            
        private Singleton() {
        }
         
        public static Singleton getInstance() {
            return instance;
        }
    }
    

    最简单的实现方式,但是如果对象的构造耗费时间,可能采用懒汉式更好。

    三、懒汉式实现一

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

    也是很简单粗暴的懒汉式实现方式,每次获取单例的时候都需要获取排他锁,效率差。

    四、懒汉式实现二

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

    1、双重判断

    • 第一次判断是防止实例化完毕后的无效同步处理
    • 如果没有第二次校验,假设线程t1执行了第一次校验后,判断为null,这时t2也获取了CPU执行权,也执行了第一次校验,判断也为null。接下来t2获得锁,创建实例。这时t1又获得CPU执行权,由于之前已经进行了第一次校验,结果为null(不会再次判断),获得锁后,直接创建实例。结果就会导致创建多个实例。所以需要在同步代码里面进行第二次校验,如果实例为空,则进行创建。

    2、指令重排

    指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。
    也就是说,JVM为了执行效率会将指令进行重新排序,但是这种重新排序不会对单线程程序产生影响。

    由于instance = new Singleton();操作并不是一个原子性指令,会被分为多个指令:

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

    但是经过重排序后如下:

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

    3、可见性

    instance需要加volatile关键字,否则会出现错误。问题的原因在于JVM指令重排优化的存在。在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间(空白内存)并将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化。若紧接着另外一个线程来调用getInstance,取到的就是状态不正确的对象,程序就会出错。

    五、懒汉式实现三

    public class Singleton {
        private static class SingletonHolder {
            private static Singleton instance = new Singleton();
    
            private SingletonHolder(){
            }
        }
    
        public static Singleton getInstance() {
            return SingletonHolder.instance;
        }
    }
    

    1、加载时机

    内部静态类是要在有引用了以后才会装载到内存的,所以在你第一次调用getInstance()之前,SingletonHolder是没有被装载进来的,只有在你第一次调用了getInstance()之后,里面涉及到了return SingletonHolder.instance; 产生了对SingletonHolder的引用,内部静态类的实例才会真正装载。这也就是懒加载的意思。

    遇到new、getstatic、putstatic、或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化,生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候,读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。

    2、线程安全

    虚拟机会保证一个类的()方法在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,只到活动线程执行()方法完毕。

  • 相关阅读:
    成长
    mui组件通用CSS类
    CSS推荐的类名
    必须认识的http请求包
    Bootstrap技术: 如何给nav导航组件的tab页增加关闭按钮以及动态的添加和关闭tab页
    bootstrap 关闭tabs
    typescirpt 知识点
    手动使用gulp4.0配合rollup搭建typescript 写一个自己的一个开源库(起步 构建自动化处理,热更新浏览器)
    Wuss Weapp 一款高质量,组件齐全,高自定义的微信小程序 UI 组件库
    wussUI v1.0.0小程序UI组件库 第一期开发已完成
  • 原文地址:https://www.cnblogs.com/mrcharleshu/p/13369595.html
Copyright © 2011-2022 走看看