• 07 创建型模式-单例模式


    1.单例模式介绍

    单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,此模式保证
    某个类在运行期间,只有一个实例对外提供服务,而这个类被称为单例类。

    2.使用单例模式要做的两件事
    1. 保证一个类只有一个实例
    2. 为该实例提供一个全局访问节点
    3. 单例模式结构

    在这里插入图片描述

    4.1 单例模式之饿汉式:

    类加载期间初始化静态实例,保证 instance 实例的创建是线程安全的 ( 实例在
    类加载时实例化,有JVM保证线程安全).
    特点: 不支持延迟加载实例(懒加载) , 此中方式类加载比较慢,但是获取实例对象
    比较快。

    问题: 该对象足够大的话,而一直没有使用就会造成内存的浪费。

    public class Singleton_01 {
    
    	//1. 私有构造方法
    	private Singleton_01(){}
    
    	//2. 在本类中创建私有静态的全局对象
    	private static Singleton_01 instance = new Singleton_01();
    
    	//3. 提供一个全局访问点,供外部获取单例对象
    	public static Singleton_01 getInstance(){
    		return instance;
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    4.2懒汉式(线程不安全)

    此种方式的单例实现了懒加载,只有调用getInstance方法时 才创建对象.但是如
    果是多线程情况,会出现线程安全问题.

    public class Singleton_02 {
    
    	//1. 私有构造方法
    	private Singleton_02(){}
    
    	//2. 在本类中创建私有静态的全局对象
    	private static Singleton_02 instance;
    
    	//3. 通过判断对象是否被初始化,来选择是否创建对象
    	public static Singleton_02 getInstance(){
    		if(instance == null){
    			instance = new Singleton_02();
    		}
    		return instance;
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    注意:
    假设在单例类被实例化之前,有两个线程同时在获取单例对象,线程A在执
    行完if (instance == null) 后,线程调度机制将 CPU 资源分配给线程B,此
    时线程B在执行 if (instance == null)时也发现单例类还没有被实例化,这样
    就会导致单例类被实例化两次。为了防止这种情况发生,需要对
    getInstance() 方法同步处理。改进后的懒汉模式.

    懒汉式(线程安全)

    原理: 使用同步锁 synchronized 锁住 创建单例的方法 ,防止多个线程同时调
    用,从而避免造成单例被多次创建。

    public class Singleton_03 {
    
    	//1. 私有构造方法
    	private Singleton_03(){}
    
    	//2. 在本类中创建私有静态的全局对象
    	private static Singleton_03 instance;
    
    	//3. 通过添加synchronize,保证多线程模式下的单例对象的唯一性
    	public static synchronized Singleton_03 getInstance(){
    		if(instance == null){
    			instance = new Singleton_03();
    		}
    		return instance;
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    缺点
    懒汉式的缺点也很明显,我们给 getInstance() 这个方法加了一把大锁
    (synchronzed),导致这个函数的并发度很低。量化一下的话,并发度
    是 1,也就相当于串行操作了。而这个函数是在单例使用期间,一直会被调
    用。如果这个单例类偶尔会被用到,那这种实现方式还可以接受。但是,
    如果频繁地用到,那频繁加锁、释放锁及并发度低等问题,会导致性能瓶
    颈,这种实现方式就不可取了。

    双重校验

    饿汉式不支持延迟加载,懒汉式有性能问题,不支持高并发。那我们再来看一
    种既支持延迟加载、又支持高并发的单例实现方式,也就是双重检测实现方
    式。

    实现步骤:

    1. 在声明变量时使用了 volatile 关键字,其作用有两个:
      保证变量的可见性:当一个被volatile关键字修饰的变量被一个线程修改的时
      候,其他线程可以立刻得到修改之后的结果。
      屏蔽指令重排序:指令重排序是编译器和处理器为了高效对程序进行优化的手
      段,它只能保证程序执行的结果时正确的,但 是无法保证程序的操作顺序与代
      码顺序一致。这在单线程中不会构成问题,但是在多线程中就会出现问题。
    2. 将同步方法改为同步代码块. 在同步代码块中使用二次检查,以保证其不被
      重复实例化 同时在调用getInstance()方法时不进行同步锁,效率高。
    public class Singleton_04 {
    
    	//1. 私有构造方法
    	private Singleton_04(){}
    
    	//2. 使用 volatile保证变量的可见性
    	private volatile static Singleton_04 instance = null;
    
    	//3. 对外提供静态方法获取对象
    	public static Singleton_04 getInstance(){
    		//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
    		if(instance == null){
    			synchronized (Singleton_04.class){
    				//抢到锁之后再次进行判断是否为null
    				if(instance == null){
    					instance = new Singleton_04();
    				}
    			}
    		}
    		return instance;
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    静态内部类

    原理 根据静态内部类 的特性(外部类的加载不影响内部类),同时解决了按
    需加载、线程安全的问题,同时实现简洁。

    1. 在静态内部类里创建单例,在装载该内部类时才会去创建单例
    2. 线程安全:类是由 JVM 加载,而 JVM 只会加载1遍,保证只有1个单例
    public class Singleton_05 {
    
    	private static class SingletonHandler{
    		private static Singleton_05 instance = new Singleton_05();
    	}
    
    	private Singleton_05(){}
    
    	public static Singleton_05 getInstance(){
    		return SingletonHandler.instance;
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    反射对于单例的破坏

    反射技术过于强大,它可以通过 setAccessible() 来修改构造器,字段,方法
    的可见性。单例模式的构造方法是私有的,如果将其可见性设为 public ,那么
    将无法控制对象的创建。

    public class Test_Reflect {
    
    	public static void main(String[] args) {
    		try {
    			//反射中,欲获取一个类或者调用某个类的方法,首先要获取到该类的Class 对象。
    			Class<Singleton_05> clazz = Singleton_05.class;
    			//getDeclaredXxx: 不受权限控制的获取类的成员.
    			Constructor c = clazz.getDeclaredConstructor(null);
    			//设置为true,就可以对类中的私有成员进行操作了
    			c.setAccessible(true);
    			Object instance1 = c.newInstance();
    			Object instance2 = c.newInstance();
    			System.out.println(instance1 == instance2);
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    解决方法之一: 在单例类的构造方法中 添加判断 instance != null 时,直接抛
    出异常

    public class Singleton_05 {
    	private static class SingletonHandler{
    		private static Singleton_05 instance = new Singleton_05();
    	}
    	private Singleton_05(){
    		if(SingletonHandler.instance != null){
    			throw new RuntimeException("不允许非法访问!");
    		}
    	}
    	public static Singleton_05 getInstance(){
    		return SingletonHandler.instance;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    上面的这种方式使代码简洁性遭到破坏,设计不够优雅.

    序列化对于单例的破坏
    public class Test_Serializable {
    	@Test
    	public void test() throws Exception{
    		//序列化对象输出流
    		ObjectOutputStream oos = new ObjectOutputStream(newFileOutputStream("tempFile.obj"));
    		oos.writeObject(Singleton.getInstance());
    		//序列化对象输入流
    		File file = new File("tempFile.obj");
    		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
    		Singleton Singleton = (Singleton) ois.readObject();
    		System.out.println(Singleton);
    		System.out.println(Singleton.getInstance());
    		//判断是否是同一个对象
    		System.out.println(Singleton.getInstance() == Singleton);//false
    	}
    }
    
    class Singleton implements Serializable{
    	private volatile static Singleton singleton;
    
    	private Singleton() {}
    
    	public static Singleton getInstance() {
    		if (singleton == null) {
    			synchronized (Singleton.class) {
    				if (singleton == null) {
    					singleton = new Singleton();
    				}
    			}
    		}
    		return singleton;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    输出结构为false,说明:
    通过对Singleton的序列化与反序列化得到的对象是一个新的对象,这就 破坏了
    Singleton的单例性 。

    解决方案:

    /**
    * 解决方案:只要在Singleton类中定义readResolve就可以解决该问题
    * 程序会判断是否有readResolve方法,如果存在就在执行该方法,如果不存在-
    -就创建一个对象
    */
    private Object readResolve() {
    	return singleton;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    readOrdinaryObject方法的代码片段

    private Object readOrdinaryObject(boolean unshared)
    throws IOException
    {
    //此处省略部分代码
    Object obj;
    try {
    //通过反射创建的这个obj对象,就是本方法要返回的对象,也
    可以暂时理解为是ObjectInputStream的readObject返回的对象。
    //isInstantiable:如果一个serializable的类可以在运行
    时被实例化,那么该方法就返回true
    //desc.newInstance:该方法通过反射的方式调用无参构造方
    法新建一个对象。
    obj = desc.isInstantiable() ? desc.newInstance()
    : null;
    } catch (Exception ex) {
    throw (IOException) new InvalidClassException(
    desc.forClass().getName(),
    "unable to create instance").initCause(ex);
    }
    return obj;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    5 枚举(推荐方式)

    在这里插入图片描述

    public enum Singleton_06{
    
    	INSTANCE;
    	private Object data;
    
    	public Object getData() {
    		return data;
    	}
    
    	public void setData(Object data) {
    		this.data = data;
    	}
    
    	public static Singleton_06 getInstance(){
    		return INSTANCE;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述
    在这里插入图片描述

  • 相关阅读:
    阿里秀MySQL高端玩法,300页亿级MySQL调优文档GitHub开源即巅峰
    [springboot专栏]使用redisson实现分布式锁
    【校招VIP】java语言同步之锁LOCK
    集合框架1
    深度学习 | Pytorch深度学习实践 (Chapter 1~9)
    K8S入门前奏之VMware虚拟机网络配置
    如何实现sm3加密
    Python Opencv实践 - 凸包检测(ConvexHull)
    手机如何压缩照片?压缩方法分享
    c++——爆肝7000字的高精度算法大合集&&实战应用
  • 原文地址:https://blog.csdn.net/weixin_39563769/article/details/133905381