zoukankan      html  css  js  c++  java
  • 单例模式,你会写几种?

    定义:

      单例模式(singleton),保证一个类仅有一个实例,并且提供一个访问它的全局访问点。
      这句话很好理解,今天我们的重点也不在于如何解读单例模式。
      在面试的过程中,往往会遇到考察手写单例模式的场景,今天让我们关注一下,写单例模式的几种方法。

    饿汉式:

    /**
     * 饿汉式.
     *
     * @author jialin.li
     * @date 2019-12-30 22:13
     */
    public class Singleton {
        private Singleton() {
        }
    
        private static Singleton singleton = new Singleton();
    
        public Singleton getInstance(){
            return singleton;
        }
    }
      • 饿汉式的特点是类初始化的时候,创建了该对象。
      • 由于类只会初始化一次,所以保证了对象只会被创建一次。
      • 同时将构造方法私有化,保证了没有办法从外部创建对象。
      这种方法的问题是,如果该实例从始至终都没有被使用过,就会造成内存的浪费。

    懒汉式:

    /**
     * 懒汉式.
     *
     * @author jialin.li
     * @date 2019-12-30 22:13
     */
    public class Singleton {
        private Singleton() {
        }
    
        private volatile Singleton singleton = null;
    
        public Singleton getInstance(){
            // 提高性能,降低线程进入临界区的可能
            if(singleton == null){
                synchronized (Singleton.class){
                    if(singleton == null){
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    }
      这种写法又被成为双检锁模式,是一种实现单例模式的经典写法。
      代码中有两处判空:
      1. 第一处判空,是为了提高性能,降低线程进入临界区的可能性。
      2. 第二处判空是为了线程同步,假如没有第二处判空,则可能两个线程都通过了if(singleton==null)条件,这样即使是临界区内只有一个线程在执行,临界区内的代码也会被执行两遍,这样就会产生两个对象,不符合单例模式。
      成员变量使用了volatile进行修饰,一方面是保证了对象在多线程环境下的可见性,另一方面是为了防止new Singleton()进行指令重排序而导致的并发问题。
      volatile关键字的作用两个:
      1. 保证变量在线程之间的可见性(直接从主存中读写数据,不经过工作内存)
      2. 阻止编译时和运行时的指令重排,编译时JVM编译器遵循内存屏障约束,运行时依赖CPU屏障来阻止指令重排。
      指令重排是指JVM在编译Java代码的时候,或者CPU在执行JVM字节码的时候,对现有的指令顺序进行重新排序。
      指令重排的目的是为了在不改变程序执行结果的前提下,优化程序的运行效率。需要注意的是,这里所说的不改变执行结果,指的是不改变单线程下的程序执行结果。
    这里不太好懂,举一个例子,正常的new Singleton()创建步骤是:
    1. 开辟一块内存空间
    2. 创建对象
    3. 将对象的地址存入引用变量
      经过指令重排后,可能变成了:
    1. 开辟一块内存空间
    2. 将对象的地址存入引用变量
    3. 创建对象

      假设发生了指令重排,线程A、B都执行这段代码,线程A执行到了new Singleton()的步骤2,此时还没有创建对象,这个时候发生了线程的切换。线程B开始执行,这个时候线程B还可以通过if(singleton == null)的判断,因为线程A中的singleton只是指向了一个空的内存地址,这个时候线程B创建出了一个Singleton对象,当线程切换成A时,线程A仍执行了new Singleton()的步骤3,此时创建了2个Singleton对象,不符合单例模式。

    静态内部类单例模式:

    /**
     * 静态内部类单例模式.
     *
     * @author jialin.li
     * @date 2019-12-30 22:13
     */
    public class Singleton {
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            return Inner.singleton;
        }
    
        private static class Inner {
            private static Singleton singleton = new Singleton();
        }
    }
      这里利用的是内部类的特性,只有第一次调用getInstance方法的时候,虚拟机才会加载Inner并初始化singleton,并且只初始化一次。这种方法也是一种懒汉式的写法,只有在需要的时候,才创建对象。

    枚举单例模式

      枚举类也是一种单例模式
    public enum Singleton  {
        INSTANCE 
     
        //doSomething 该实例支持的行为
          
        //可以省略此方法,通过Singleton.INSTANCE进行操作
        public static Singleton get Instance() {
            return Singleton.INSTANCE;
        }
    }
    这种写法较为简单,并且没有办法用反射的方式,创建对象。但是可读性较差。

    期待您的关注、推荐、收藏,同时也期待您的纠错和批评。

  • 相关阅读:
    samba linux windows 请联系管理员
    centos chrome
    centos6.5 中文
    Hibernate 中update hql语句
    嵌入式Linux应用程序开发环境搭建记录
    JDK-windows7环境变量配置-亲测版本 以及HelloWorld
    windows tcp端口映射或端口转发
    VMWare Workstation:局域网PC连接虚拟机里的远程桌面或端口
    正则提取文本中的颜色值 #xxxx,不严谨版本
    Python 2.7.3 urllib2.urlopen 获取网页出现乱码解决方案
  • 原文地址:https://www.cnblogs.com/nedulee/p/12122153.html
Copyright © 2011-2022 走看看