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知识库 -> Java 设计模式之单例模式 -> 正文阅读

[Java知识库]Java 设计模式之单例模式

单例模式

定义:保证一个类有且仅有一个实例,并提供一个全局访问点

适用场景:想确保任何情况下都绝对只有一个实例

优点

  • 在内存里只有一个实例,减少了内存开销
  • 可以避免对资源的多重占用
  • 设置全局访问点,严格控制访问

缺点:没有接口,扩展困难

特点

  • 私有构造器(即被 private 修饰构造方法)
  • 线程安全
  • 延迟加载
  • 序列化和反序列化安全、
  • 反射

饿汉式单例

饿汉式单例是类进行初始化的时候,就已经把对象创建好了,并且使用 final 修饰,因为 final 关键字在类初始化时就必须把变量初始化好,并且不可改变,很符合单例模式的特征。

public class HungrySingleton {

    private final static HungrySingleton instance;

    static {
        instance = new HungrySingleton();
    }

    private HungrySingleton() {
    }

    public static HungrySingleton getInstance() {
        return instance;
    }
}

懒汉式单例

注重的是 延时加载 ,就意味着只有在使用它的时候,才开始初始化,不使用则不会初始化,
以下是线程不安全的懒汉式单例模式代码示例

/**
 * @author Hyxiao
 * @date 2022/3/15 17:02
 * @description 单例模式-懒汉模式(懒->初始化的时候没有创建对象)
 */
public class LazySingleton {

    private static LazySingleton instance = null;
    private LazySingleton() {}
    
    public static LazySingleton getInstance(){
        if (instance == null){
            instance = new LazySingleton();
        }
        return instance;
    }

}

线程安全的懒汉式单例模式代码示例

public class LazySingleton {

    private static LazySingleton instance = null;
    private LazySingleton() {}
    
    public synchronized static LazySingleton getInstance(){
        if (instance == null){
            instance = new LazySingleton();
        }
        return instance;
    }

}

需要留意的是,当用 synchronized 修饰静态 static 方法时,相当于锁的是 LazySingleton 的class文件,也就是把这个类给锁住了;而用 synchronized 修饰普通方法时,锁的是在堆内存中生成的对象。

等同于以下这种写法(锁住了整个类)

public class LazySingleton {

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

}

双重检查懒汉式单例

public class LazyDoubleCheckSingleton {

    private static LazyDoubleCheckSingleton instance = null;

    private LazyDoubleCheckSingleton() {}

    public static LazyDoubleCheckSingleton getInstance() {
        if (instance == null) { 
            synchronized (LazyDoubleCheckSingleton.class) {
                if (instance == null) {
                    instance = new LazyDoubleCheckSingleton();
                }
            }
        }
        return instance ;
    }

}

这种写法有隐患,具体体现在 if (instance == null) {instance = new LazyDoubleCheckSingleton();这两个地方

程序会先对进来的 instance 对象进行判空,会出现 instance 不为 null 的情况,这时候虽然 instance 是不为 null 的,但是 instance 还没有完成初始化的过程,就是 instance = new LazyDoubleCheckSingleton(); 没有执行完。针对这种 instance 会为 null 实例的场景,为此我们提出两种解决方案,一种是 保证类初始化过程的有序性 ,另一种是 类初始化时,隔离其他线程干扰

通常当我们创建并初始化一个 LazyDoubleCheckSingleton 对象时,正常情况下要经历以下三个步骤:

instance = new LazyDoubleCheckSingleton();
  1. 分配内存给这个对象
  2. 初始化对象
  3. 设置 instance指向步骤 1 刚分配好的内存地址

但是按上面所说的特殊情况,程序可能会碰到当执行完步骤 1 后,步骤 2 和 3 很有可能会出现顺序颠倒,也就是重排序,也就是下面这种情况

  1. 分配内存给这个对象
  2. 设置 instance 指向步骤 1 刚分配好的内存地址
  3. 初始化对象

所以,当出现重排序情况时, 也就是 instance 已经指向分配好的内存地址,但是 instance 它是没有初始化完成的。也就是说在多线程并发的情况下,其他线程进来拿到 instance,由于 instance 已经分配好了内存地址,所以 instance不为 null ,就直接返回 instance这个没有初始化的实例,系统就会报异常。

而对于单线程情况下,这种重排序的特殊情况,是不会有什么影响的,不会改变程序的执行结果,Java 语言规范是允许那些在单线程内不会改变单线程程序执行结果的重排序,因为单线程下的重排序,反而能提高执行性能。

保证类初始化过程的有序性

为了避免出现这种步骤 2 和 3 重排序的问题,我们可以通过 volatile 关键字来修饰 instance 来实现线程安全的延迟初始化,从而禁止重排序。如下示例代码,在声明 instance 实例时采用 volatile 来修饰,来保证步骤 2 和 3 的有序执行,防止出现重排序

private volatile static LazyDoubleCheckSingleton instance = null;

类初始化时,隔离其他线程干扰

除了使用 volatile 来限制重排序以外,我们还能通过静态内部类的方式;因为 JVM 在类的初始化阶段,会去获取一个锁,这个锁会同步多个线程对一个类的初始化。这样当其中一个线程在创建并初始化一个单例类的时候,其他线程是无法得知这个类的具体情况的,这也就保证了即使出现重排序,但是其他线程也无法获得到这个类的实例。

public class StaticInnerClassSingleton {

    private StaticInnerClassSingleton(){}
    
    private static class InnerClass {
        private static StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
    }

    public static StaticInnerClassSingleton getInstance() {
        return InnerClass.innerClassSingleton;
    }

}
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-17 21:56:42  更:2022-03-17 21:58:35 
 
开发: 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 8:30:21-

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