zoukankan      html  css  js  c++  java
  • 单例模式你真的会了吗(上篇)?

    单例模式相信是很多程序员接触最多的了,也是面试过程中考察最频繁的一个了,不知道你有没有被问过这道面试题?欢迎留言讨论。

    今天我们来重点讨论一下单例的几个问题,及如何正确的实现一个单例,然后你再来回顾一下,你之前的回答或者使用方式是否正确。

    为何要使用单例

    单例非常简单,一个类只允许创建一个对象或者实例,这个类就是一个单例类。这种设计模式就叫做单例设计模式,是创建型的第一种设计模式,简称单例模式。

    单例模式什么时候使用呢?又或者说这种情况下为什么要使用单例?

    1. 解决资源冲突问题
      比如说,我们现在的java处理程序中是使用打印机,而我们的服务端程序是多线程的,但是打印机只有一个,不能重复创建打印机资源啊。当然我们也可以定义普通类,在调用打印添加synchronized关键字。
    2. 全局唯一类
      有时候,我们做业务设计时,有些数据在系统中只应该保留一份,这时候就应该设计为单例。
      比如配置信息类,系统的配置文件应该只有一份,加载到内存之后以对象的形式存在,理所应当只有一份。
      再比如说,我们设计一个抽奖系统,每点击一次生成一个抽奖序号,可以设计一个单例,内部存储好所有的序号,每次随机取出一个序号。如果使用普通类对象的话,那就需要通过共享内存共享所有抽奖序号。

    单例应该怎么写?

    学习任何东西,因为大脑的容量是有限的,首先我们要理解概念,知道为什么,来后追求怎么做,怎么实现,做的过程可能很复杂,比如有一二三四五步骤,但我们要化繁为简,概括精简。

    单例需要考虑以下几个问题:

    • 构造函数要是private的,这样才能避免外部通过new创建实例嘛,不然怎么叫单例,别人可以随便通过new来创建啊。
    • 多线程创建时是否有线程安全问题。
    • 支持延迟加载吗?
    • getInstance()性能高吗?

    单例典型实现方式

    饿汉式

    通过这种形容方式,可以直观的理解一下,饿汉一直担心自己吃不饱,所以先吃了再说,也就是说实例是事先初始化好的,也就没有办法延迟加载了。
    不支持懒加载,有人就说这种方式不好,说我都没有使用单例,你都给我加载了,浪费啊。但是有坏处也有好处,提前把类加载进来,提前暴露问题,这样如果类的设计有问题,在程序启动时就会报错,而不是等到程序运行中才暴露出来。

    public class SingleTon {
        private static final SingleTon instance = new SingleTon();
        
        private SingleTon() {}
        
        public static SingleTon getInstance() {
            return instance;
        }
        
        public void method() {}
    }
    

    懒汉式

    所谓懒汉式,那就是支持延迟加载喽。总体思路类似,但在类内部并不是默认就把instance实例化好。

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

    为什么要加synchronized呢?如果是多线程同时调用getInstance(),会有并发问题啊,多个线程可能同时拿到instance == null的判断,这样就会重复实例化,单例就不是单例。所以为了解决多线程并发的问题,这里牺牲了性能,变成了严格的串行制。多线程下性能很低。

    双重检测懒汉式

    饿汉方式不支持延迟加载。
    懒汉方式,多线程下性能低下,那怎么修改呢,就是改进的懒汉方式,又叫双重检测。
    具体怎么做呢?

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

    这个类里的volatile十分关键,如果没有volatile关键字修饰instance变量,如果线程1执行到instance = new SingleTon();的时候,线程2此时判断instance已经不等于null了,会直接返回instance,但此时instance并未初始化完毕,为什么这么说呢?因为对象的初始化分为三步:

    • 分配内存
    • 内存初始化
    • 对象指向新分配的内存地址

    既然是分为三步,那就不是原子操作,而且可能会发生指令重排,也就是说可能先执行第三步,这时候其他线程判断instance也就不是null了。加上volatile关键字,可以禁止机器指令重排,就不会有这个问题了。

    静态内部类

    这种方式,避免双重检测,利用java静态内部类,类似饿汉方式,又做到延迟加载。

    public class SingleTon {
    
        private SingleTon() {}
        
        private static class SingleTonHolder {
            private static final SingleTon instance = new SingleTon();
        }
    
        public static SingleTon getInstance() {
            return SingleTonHolder.instance;
        }
    
        public void method() {}
    }
    

    是不是觉得很简洁?推荐大家使用这种方式,类SingleTon加载时,并不会加载SingleTonHolder类,只要调用getInstance方法时,SingleTonHolder才会被加载,并创建instance,这些都是由JVM来保证的。

    枚举方式

    还有一种更简单的,但是理解起来可能有点费解,枚举的构造函数默认就是私有的。java的枚举类型本身就保证了线程安全性和实例唯一性。
    只需要简单几行,就可以使用枚举单例INSTANCE的方法了。

    public enum SingleTon {
        INSTANCE;
        
        public void method() {}
    }
    

    但是单例模式真的就好吗?下面我们会讨论一下为什么不推荐单例模式?如何替代,以及如何做到集群下的分布式单例模式?

    程序员的小伙伴们,学习之路,同行的人越多才可以走的更远,加入公众号[程序员之道],一起交流沟通,走出我们的程序员之道!
    扫码加入吧!

  • 相关阅读:
    HDU 2955 Robberies(01背包)
    HDU 2602 Bone Collector(01背包)
    HUST 1352 Repetitions of Substrings(字符串)
    HUST 1358 Uiwurerirexb jeqvad(模拟解密)
    HUST 1404 Hamming Distance(字符串)
    HDU 4520 小Q系列故事――最佳裁判(STL)
    HDU 2058 The sum problem(枚举)
    【破解】修改程序版权、添加弹窗
    HDU 1407 测试你是否和LTC水平一样高(枚举)
    HDU 1050 Moving Tables(贪心)
  • 原文地址:https://www.cnblogs.com/chanllenge/p/12439289.html
Copyright © 2011-2022 走看看