• 02. 01-单例模式(singleton)


    1. 单例设计模式介绍

    就是采取一定的方法,保证在整个软件系统中,对某个类只能存在一个实例对象,并且该类只提供一个获取其对象实例的方法(静态方法).

    如果你创建了一个对象, 过一会之后决定再创建一个对象,你就会获得之前已经创建的对象,而不是一个新对象.
    所以必须为该实例提供一个全局访问点.


    • 实现方法
    1. 在类中添加一个私有的静态成员变量用于保存单例实例

    • 步骤
    1. 将默认的构造函数设为私有,防止其他对象使用单例类的new运算符
    2. 新建一个静态构建方法作为构造函数,该函数会偷偷调用私有构造函数来穿件对象,并将其保存在一个静态成员变量中,此后所有对于该函数的调用都将返回这一缓存对象

    2. 单例设计模式的8种方法

    后三种是推荐使用的,饿汉式看情况使用

    1. 饿汉式(静态常量)

    1. 构造器私有化(防止外部new一个对象)
    2. 类的内部创建对象
    3. 向外暴露一个静态的公共方法
    class Singleton {
    	
    	//1. 构造器私有化, 外部不能new
    	private Singleton() {
    		
    	}
    	//2.本类内部创建对象实例
    	//final 在定义时必须初始化,不能被修改
    	private final static Singleton instance = new Singleton();
    	//3. 提供一个公有的静态方法,返回实例对象
    	public static Singleton getInstance() {
    		return instance;
    	}
    	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 优缺点说明 :

    优点 : 写法比较简单,就是在类装载的时候就完成实例化,避免了线程同步问题(类装载的时候是线程安全的)
    缺点 : 在类装载的时候就完成实例化,没有达到lazy loading的效果,如果一直没有用过这个实力,则会造成内存浪费

    • 这种方式基于类加载的机制,避免了多线程的同步问题,不过,instance在类装载时就实例化,没有达到lazy loading的效果
    • 结论 :

    这种单例可用,可能造成内存浪费.


    2. 饿汉式(静态代码块)

    class Singleton {
    	//1. 构造器私有化, 外部不能new
    	private Singleton() {
    		
    	}
    	//2.本类内部创建对象实例
    	private  static Singleton instance;
    	static { // 在静态代码块中,创建单例对象
    		//静态代码块在类加载的时候执行,且值执行一次
    		instance = new Singleton();
    	}
    	//3. 提供一个公有的静态方法,返回实例对象
    	public static Singleton getInstance() {
    		return instance;
    	}
    	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 优缺点说明

    和上面1中的一样,只不过类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行了静态代码块中的代码,初始化类的实例.

    • 结论

    和1.饿汉式(静态常量)一样


    3. 懒汉式(线程不安全)

    class Singleton {
    	private static Singleton instance;
    	
    	private Singleton() {}
    	
    	//提供一个静态的公有方法,当使用到该方法时,才去创建 instance
    	//即懒汉式
    	public static Singleton getInstance() {
    		if(instance == null) {
    			instance = new Singleton();
    		}
    		return instance;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 优缺点说明:
    1. 起到了lazy loading的效果,但是只能在单线程下使用
    2. 如果在多线程下,一个线程进入了if(singleton == null)判断语句块,还来不及往下执行,另一个线程也通过了这个判断语句,这时会产生多个实例,所以在多线程环境下不能使用这种方式

    -结论

    在实际开发中,不要使用这种方式


    4. 懒汉式(线程安全,同步方法)

    
    // 懒汉式(线程安全,同步方法)
    class Singleton {
    	private static Singleton instance;
    	
    	private Singleton() {}
    	
    	//提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
    	//即懒汉式
    	public static synchronized Singleton getInstance() {
    		if(instance == null) {
    			instance = new Singleton();
    		}
    		return instance;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 优缺点说明:
    1. 解决了线程安全的问题
    2. 效率太低了,每个线程在想获得该类实例的时候,执行getInstance()都要进行同步,而其实这个方法只要一次实例化代码就可以了,后面想要获得该类的实例,直接return就行了.方法进行同步效率太低
    • 结论

    在实际开发中,不推荐使用这种方式

    5.懒汉式(线程安全,同步代码块)

    class Singleton {
    	private static Singleton instance;
    
    	private Singleton() {}
    
    	//提供一个静态的公有方法,当使用到该方法时,才去创建 instance
    	//即懒汉式
    	public static Singleton getInstance() {
    		if(instance == null) {
    			synchronized (Singleton.class) {
    				instance = new Singleton();
    			}
    		}
    		return instance;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 其实也是线程不安全的,如果同时有几个线程进入了 if(instance == null) 代码块中,虽然同步了,但是也会生成不同的实例对象

    • 结论

    不推荐使用


    6.双重检查

    class Singleton {
    	private static volatile Singleton instance;
    	
    	private Singleton() {}
    	
    	//提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题
    	//同时保证了效率, 推荐使用
    	
    	public static synchronized Singleton getInstance() {
    		if(instance == null) {
    			synchronized (Singleton.class) {
    				if(instance == null) {
    					instance = new Singleton();
    				}
    			}
    			
    		}
    		return instance;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 优缺点说明
    1. double-check概念是多线程开发中常用到的,
    2. 这样实例化代码值执行了一次,后面再次访问时,判断if(singleton == null),直接return实例化对象,也避免的反复进行同步方法
    3. 线程安全: 延迟加载,效率比较高
    • 结论

    在实际开发中,推荐使用这种方法


    7.静态内部类

    // 静态内部类完成, 推荐使用
    class Singleton {
    	private static volatile Singleton instance;
    	
    	//构造器私有化
    	private Singleton() {}
    	
    	//写一个静态内部类,该类中有一个静态属性 Singleton
    	private static class SingletonInstance {
    		private static final Singleton INSTANCE = new Singleton(); 
    	}
    	
    	//提供一个静态的公有方法,直接返回SingletonInstance.INSTANCE
    	
    	public static synchronized Singleton getInstance() {
    		//调用该类的方法时加载类,做到了线程同步
    		return SingletonInstance.INSTANCE;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 优缺点说明:
    1. 这种方式采用了类装载的机制来保证初始化实例时只有一个线程
    2. 静态内部类方式在Singleton类被加载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化
    3. 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程安全性,在类进行初始化时,别的线程是无法进入的.
    4. 优点: 避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
    • 结论:

    推荐使用


    8.枚举

    //使用枚举,可以实现单例, 推荐
    enum Singleton {
    	INSTANCE; //属性
    	public void sayOK() {
    		System.out.println("ok~");
    	}
    }
    
    //使用
    Singleton instance = Singleton.INSTANCE;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 优缺点说明:

    借助JDK1.5中添加的枚举来实现单例模式,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象

    • 结论:

    推荐使用


    3. 单例模式注意事项和细节说明

    1. 单例模式保证了内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
    2. 当想实例化一个实例对象的时候,必须要使用相应的获取对象的方法,而不是使用new
    3. 单例模式的使用场景:

    需要频繁进行创建和销毁的对象
    创建对象时耗时过多或消耗资源过多(重量级对象),但是又需要经常使用到的对象,工具类对象,频繁访问数据库或文件的对象


    4. 和其它模式的关系

    • 外观模式类 通常可以转换为单例模式,因为在大部分情况下一个外观对象就足够了.
    • 如果你能将对象的所有共享状态简化为一个享元对象, 那么享元模式就和单例类似了。 但这两个模式有两个根本性的不同。
    1. 只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同。
    2. 单例对象可以是可变的。 享元对象是不可变的。
    • 抽象工厂模式、 生成器模式和原型模式都可以用单例来实现。
  • 相关阅读:
    算法题:SOJ1092: 欧几里得算法
    大数据Spark面试题2023
    记一次 .NET 某游戏服务后端 内存暴涨分析
    Git(七).git 文件夹瘦身,GitLab 永久删除文件
    PTA 7-87 A±B
    AUTOSAR从入门到精通-汽车CAN网络信息安全研究(续)
    算法系列九:十大经典排序算法之——快速排序
    buuctf_练[GYCTF2020]FlaskApp
    《最新出炉》系列初窥篇-Python+Playwright自动化测试-14-playwright操作iframe-番外篇
    volatile类型变量提供什么保证?
  • 原文地址:https://blog.csdn.net/squallmouse/article/details/126095521