• 设计模式-单例模式


    模式意图

    保证一个类只有一个实例,并提供一个全局访问点,目的就是为了节省资源,节省cpu资源,节省内存,并且自行实例化并向整个系统提供这个对象。        

    场景

    需要更严格的控制全局变量,避免多个对象操作使变量数据不准确。

    重量级的对象如线程池对象,数据库连接池对象。

    不需要多个实例对象的工具类。

    特点

    单例模式是创建者模式,重点关注怎样创建对象,最大的特点是将对象的创建和使用分离。

    • 单例模式只有一个实例对象
    • 单例模式对象必须由单例类自行创建
    • 单例类向外提供一个访问该单例的全局访问点

    懒汉式

    用的时候再把对象初始化 

    1. public class Singleton {
    2. private static Singleton instance;
    3. private Singleton(){
    4. }
    5. public static Singleton getInstance2(){
    6. if(instance == null){
    7. instance = new Singleton();
    8. }
    9. return instance;
    10. }
    11. }

    优点:延迟加载,节省资源

    缺点:线程不安全

     

     改进1:为了线程安全,为方法添加锁

    1. public class Singleton {
    2. private static Singleton instance = new Singleton();
    3. //避免从外部new对象
    4. private Singleton(){
    5. }
    6. public synchronized static Singleton getInstance1(){
    7. if(instance == null){
    8. instance = new Singleton();
    9. }
    10. return instance;
    11. }
    12. }

    饿汉式只有创建对象是才有线程安全问题,一旦创建了就不存在线程安全问题了,如果每次获取对象都有锁,会造成获取对象的效率下降。

    改进2:为了提高效率,将锁放到具体的判断代码上

    1. public class Singleton {
    2. private static Singleton instance;
    3. private Singleton(){
    4. }
    5. public static Singleton getInstance2(){
    6. if(instance == null){
    7. synchronized(Singleton.class){
    8. instance = new Singleton();
    9. }
    10. }
    11. return instance;
    12. }
    13. }
    如果AB两个线程都执行完了instance == null,然后进入到了if内,A执行完创建对象后,B还是会执行创建对象,也是线程不安全的。

    改进3:再次为了线程安全问题,增加了一个判断条件

    1. public class Singleton {
    2. private static Singleton instance;
    3. private Singleton(){
    4. }
    5. public synchronized static Singleton getInstance2(){
    6. if(instance == null){
    7. synchronized (Singleton.class){
    8. if(instance == null){
    9. instance = new Singleton();
    10. }
    11. }
    12. }
    13. return instance;
    14. }
    15. }

    为什么还需要在锁外面再加一个判断条件?

    是为了避免每个线程进来都要加持锁从而降低效率,通过if判断效率更快。

    如果AB线程同时进入第一个if条件内部,A进入执行完锁的内容,B进去后再执行一次条件,所以解决了线程安全问题。

    其实还是存在问题

    创建对象的操作并不是原子操作,首先要分配对象内存空间,再初始化对象,再设置变量去指向对象的内存空间。这三个步骤在JVM执行时可能会出现指令重排的问题。

     改进4:添加volatile关键字,防止指令重排

    1. public class Singleton {
    2. private static volatile Singleton instance;
    3. private Singleton(){
    4. }
    5. public synchronized static Singleton getInstance2(){
    6. if(instance == null){
    7. synchronized (Singleton.class){
    8. if(instance == null){
    9. instance = new Singleton();
    10. }
    11. }
    12. }
    13. return instance;
    14. }
    15. }

     

    饿汉式

    类加载的时候就把对象初始化 

    遵循着单例模式的特点创建单例模式类

    1. public class Singleton {
    2. private static Singleton instance;
    3. //避免从外部new对象
    4. private Singleton(){
    5. }
    6. public static Singleton getInstance1(){
    7. if(instance == null){
    8. instance = new Singleton();
    9. }
    10. return instance;
    11. }
    12. }

    优点:饿汉式是线程安全的,因为在调用前就有了对象

    缺点:如果单例类中还要其他的静态方法,如果大部分时候都在调用其他的静态方法,但是这个对象一直加载在内存中,就会造成资源的浪费。

    静态内部类方式

    1. public class Singleton {
    2. public static class Inner{
    3. private static final Singleton INSTANCE = new Singleton();
    4. }
    5. public static Singleton getInstance(){
    6. return Inner.INSTANCE;
    7. }
    8. }

    优点:天然的线程安全,静态内部类不会随着外部类的加载和初始化而初始化,它会单独的加载和初始化。如果在外部类中有一些其他的工作,相较于饿汉式的天然线程安全,这种方式更加节省资源。只有当调用getInstance方法时才会去加载内部类。

    在这里内部类保证了线程安全,外部类保证了效率。

    枚举方式

    1. public class Singleton {
    2. private Singleton(){
    3. }
    4. public static Singleton getInstance(){
    5. return InnerEnum.INSTANCE.getInstance();
    6. }
    7. //枚举意思就是实例为有限个
    8. private static enum InnerEnum{
    9. /*
    10. 因为每个枚举实例都是static final的,只会进行一次实例化
    11. 借助枚举对象只能进行一次实例化,将需要的单例对象作为枚举类的属性,进而来实现单例模式
    12. * */
    13. INSTANCE;
    14. private final Singleton instance;
    15. private Singleton getInstance(){
    16. return instance;
    17. }
    18. private InnerEnum(){
    19. instance = new Singleton();
    20. }
    21. }
    22. }

    优点:线程最安全,枚举是不能被反射或序列化被破坏线程安全。

    总结

    懒汉式是时间换空间,饿汉式是空间换时间。

  • 相关阅读:
    APP风控SDK之Android APP防作弊SDK解决方案
    玩转 CMS
    面试经典150题——Day20
    使用IntelliJ IDEA 2022.1.2版本进行maven的创建和基本的一些使用
    高并发、高可用、弹性扩展,天翼云护航企业云上业务
    关于 Python 的 import
    配置nodejs的俩小脚本
    EMQ(MQTT)安装部署简介
    面向对象是面试时提问最多的吗?
    35岁失业程序员的在线简历制作工具
  • 原文地址:https://blog.csdn.net/weixin_52972575/article/details/125562516