IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 单例模式的4种写法 -> 正文阅读

[Java知识库]单例模式的4种写法

单例模式是开发过程中常用的模式之一,首先了解下单例模式的四大原则:

  1. 构造方法私有;
  2. 以静态方法或枚举返回实例;
  3. 确保实例只有一个,尤其是多线程环境;
  4. 确保反射或反序列化时不会重新构建对象;

饿汉模式

饿汉模式在类被初始化时就创建对象,以空间换时间,故不存在线程安全问题。

public class SingleTon{
    // 创建对象
   	private static SingleTon INSTANCE = new SingleTon();
    // 构造方法私有
 	private SingleTon(){}
    // 返回实例
	public static SingleTon getInstance(){ return INSTANCE; }
}

饱汉模式

饱汉是变种最多的单例模式。我们从饱汉出发,通过其变种逐渐了解实现单例模式时需要关注的问题。

基础饱汉

public class Singleton {
  	  
    private static Singleton singleton = null;
    
    private Singleton() {}
    
  	public static Singleton getInstance() {
    	if (singleton == null) {
      		singleton = new Singleton();
    	}
    	return singleton;
  	}
}

饱汉模式的核心就是懒加载。好处是更启动速度快、节省资源,一直到实例被第一次访问,才需要初始化单例;缺点是线程不安全,if语句存在竞态条件。

DCL(Double Check Lock)

上面说到基础饱汉模式的缺点是线程不安全,在多线程环境下无法保证实例唯一,因此只需要在关键语句上加锁即可解决问题:

public class Singleton {
  	  
    private static Singleton singleton = null;
    
    private Singleton() {}
    
  	public synchronized static Singleton getInstance() {
    	if (singleton == null) {
      		singleton = new Singleton();
    	}
    	return singleton;
  	}
}

第一种解决方式简单粗暴,直接在获取实例的方法上加锁,保证了多线程情况下实例唯一,但是synchronized操作是很耗时的,导致的结果就是并发性能极差,因此可对其进行修改:

public class Singleton {
  	  
    private static Singleton singleton = null;
    
    private Singleton() {}
    
  	public static Singleton getInstance() {
    	if (singleton == null) {
      		synchronized(Singleton.class){
                if (singleton == null) {
      				singleton = new Singleton();
    			}
            }
    	}
    	return singleton;
  	}
}

这就是双重检查锁模式(DCL,Double Check Lock),在这种模式下,基本达到了理想的效果(懒加载+线程安全);

事实上,DCL模式仍存在一些问题,第11行中singleton = new Singleton()并不是一个原子操作,它在jvm中分为3步执行:

  1. memory = allocate(); //在堆内存开辟内存空间;
  2. initInstance(memory); // 在堆内存中实例化SingleTon里面的各个参数;
  3. instance = memory; //把对象指向堆内存空间。

由于jvm指令重排的机制,第3步操作是可能被优化到第2步操作之前的,此时若有新的线程进入该方法,经if判断后(因第3步操作使该对象指向了一块内存空间,所以if判断结果为false)会直接返回singleton,即引用instance指向内存memory时,该内存还未被初始化;这就是著名的DCL失效问题,解决方法也很简单,使用volatile关键字修饰即可;

Holder模式

我们既希望利用饿汉模式中静态变量的方便和线程安全;又希望通过懒加载规避资源浪费。Holder模式满足了这两点要求:核心仍然是静态变量,足够方便和线程安全;通过静态的Holder类持有真正实例,间接实现了懒加载。

public class Singleton {
    
    private static class SingletonHolder {
    	private static final Singleton singleton = new Singleton();
    	private SingletonHolder() {
    	}
  	}
    
  	private Singleton() {}

	public static Singleton getInstance() {
    	return SingletonHolder.singleton;
  	}
}

静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。即当SingleTon第一次被加载时,并不需要去加载SingleTonHoler,只有当getInstance()方法第一次被调用时,才会去初始化INSTANCE,第一次调用getInstance()方法会导致虚拟机加载SingleTonHoler类,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。

虚拟机会保证一个类的在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的方法,其他线程都需要阻塞等待,直到活动线程执行初始化方法完毕。

枚举模式

public enum Singleton {
  SINGLETON;
}

枚举在java中与普通类一样,都能拥有字段与方法,而且枚举实例创建是线程安全的,在任何情况下,它都是一个单例;但是缺点是可读性极差。
通过反编译我们可以看到枚举类的本质:
在这里插入图片描述

本质上和饿汉模式相同,区别仅在于公有的静态成员变量。

反射与反序列化攻击

以上介绍的几种模式,除了枚举模式外,其他模式均无法防止反射攻击和反序列化攻击;

反射演示:

public static void main(String[] args) throws Exception{
    SingleTon singleTon = SingleTon.getInstance();

    Constructor<SingleTon> constructor = SingleTon.class.getDeclaredConstructor();
    constructor.setAccessible(true);
    SingleTon singleTon1 = constructor.newInstance();
    System.out.println(singleTon1 == singleTon);
    // 结果为false
}

解决办法:反射是通过构造方法构建实例的,那么只需要在构造方法中加入判空即可:
在这里插入图片描述

反序列化演示(需单例类实现Serializable接口):

public static void main(String[] args) throws Exception{
    SingleTon singleTon = SingleTon.getInstance
    FileOutputStream out = new FileOutputStream("SingleTon.txt");
    ObjectOutputStream oos = new ObjectOutputStream(out);
    oos.writeObject(singleTon);
    oos.close();
    out.close
    FileInputStream in = new FileInputStream("SingleTon.txt");
    ObjectInputStream obj = new ObjectInputStream(in);
    SingleTon singleTon1 = (SingleTon) obj.readObject();
    in.close();
    obj.close();
    System.out.println(singleTon == singleTon1);
    // 输出结果为false
}

解决办法:定义readResolve()方法,反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要单独再创建新对象。
在这里插入图片描述

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-03 15:57:54  更:2022-03-03 16:00:04 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 11:52:32-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码