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() {}
    }
    

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

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

  • 相关阅读:
    在博客园里给图片加水印(canvas + drag)
    Chrome开发者工具使用指南
    《古剑奇谭3》千秋戏辅助工具(前端React制作)
    React中useEffect的源码解读
    关于为什么使用React新特性Hook的一些实践与浅见
    使用@babel/preset-typescript取代awesome-typescript-loader和ts-loader
    使用dva改造React旧项目的数据流方案
    在React旧项目中安装并使用TypeScript的实践
    安利一个绘制指引线的JS库leader-line
    微信小程序中悬浮窗功能的实现(主要探讨和解决在原生组件上的拖动)
  • 原文地址:https://www.cnblogs.com/chanllenge/p/12439289.html
Copyright © 2011-2022 走看看