User-Profile-Image
hankin
  • 5
  • Java
  • Kotlin
  • Spring
  • Web
  • SQL
  • MegaData
  • More
  • Experience
  • Enamiĝu al vi
  • 分类
    • Zuul
    • Zookeeper
    • XML
    • WebSocket
    • Web Notes
    • Web
    • Vue
    • Thymeleaf
    • SQL Server
    • SQL Notes
    • SQL
    • SpringSecurity
    • SpringMVC
    • SpringJPA
    • SpringCloud
    • SpringBoot
    • Spring Notes
    • Spring
    • Servlet
    • Ribbon
    • Redis
    • RabbitMQ
    • Python
    • PostgreSQL
    • OAuth2
    • NOSQL
    • Netty
    • MySQL
    • MyBatis
    • More
    • MinIO
    • MegaData
    • Maven
    • LoadBalancer
    • Kotlin Notes
    • Kotlin
    • Kafka
    • jQuery
    • JavaScript
    • Java Notes
    • Java
    • Hystrix
    • Git
    • Gateway
    • Freemarker
    • Feign
    • Eureka
    • ElasticSearch
    • Docker
    • Consul
    • Ajax
    • ActiveMQ
  • 页面
    • 归档
    • 摘要
    • 杂图
    • 问题随笔
  • 友链
    • Spring Cloud Alibaba
    • Spring Cloud Alibaba - 指南
    • Spring Cloud
    • Nacos
    • Docker
    • ElasticSearch
    • Kotlin中文版
    • Kotlin易百
    • KotlinWeb3
    • KotlinNhooo
    • 前端开源搜索
    • Ktorm ORM
    • Ktorm-KSP
    • Ebean ORM
    • Maven
    • 江南一点雨
    • 江南国际站
    • 设计模式
    • 熊猫大佬
    • java学习
    • kotlin函数查询
    • Istio 服务网格
    • istio
    • Ktor 异步 Web 框架
    • PostGis
    • kuangstudy
    • 源码地图
    • it教程吧
    • Arthas-JVM调优
    • Electron
    • bugstack虫洞栈
    • github大佬宝典
    • Sa-Token
    • 前端技术胖
    • bennyhuo-Kt大佬
    • Rickiyang博客
    • 李大辉大佬博客
    • KOIN
    • SQLDelight
    • Exposed-Kt-ORM
    • Javalin—Web 框架
    • http4k—HTTP包
    • 爱威尔大佬
    • 小土豆
    • 小胖哥安全框架
    • 负雪明烛刷题
    • Kotlin-FP-Arrow
    • Lua参考手册
    • 美团文章
    • Java 全栈知识体系
    • 尼恩架构师学习
    • 现代 JavaScript 教程
    • GO相关文档
    • Go学习导航
    • GoCN社区
    • GO极客兔兔-案例
    • 讯飞星火GPT
    • Hollis博客
    • PostgreSQL德哥
    • 优质博客推荐
    • 半兽人大佬
    • 系列教程
    • PostgreSQL文章
    • 云原生资料库
    • 并发博客大佬
Help?

Please contact us on our email for need any support

Support
    首页   ›   Java   ›   正文
Java

Java—单例模式

2023-01-03 22:08:28
585  0 1
参考目录 隐藏
1) 如何选择单例创建方式
2) 单例创建方式
3) 饿汉式
4) 懒汉式
5) 静态内部类
6) 枚举单例式
7) 双重检测锁方式
8) 线程安全的单例模式
9) 单例模式懒汉式和饿汉式有哪些区别

阅读完需:约 15 分钟

该如何优雅的、安全的使用单例模式呢?

什么是单例:保证一个类只有一个实例,并且提供一个访问该全局访问点

单例优缺点

优点:

  1. 在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就防止其它对象对自己的实例化,确保所有的对象都访问一个实例
  2. 单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
  3. 提供了对唯一实例的受控访问。
  4. 由于在系统内存中只存在一个对象,因此可以节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
  5. 允许可变数目的实例。
  6. 避免对共享资源的多重占用。

缺点:

  1. 不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
  2. 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
  3. 单例类的职责过重,在一定程度上违背了“单一职责原则”。
  4. 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

单例模式使用注意事项

  1. 使用时不能用反射模式创建单例,否则会实例化一个新的对象
  2. 使用懒单例模式时注意线程安全问题
  3. 饿单例模式和懒单例模式构造方法都是私有的,因而是不能被继承的,有些单例模式可以被继承(如登记式模式)

单例防止反射漏洞攻击

private static boolean flag = false;

private Singleton() {

	if (flag == false) {
		flag = !flag;
	} else {
		throw new RuntimeException("单例模式被侵犯!");
	}
}

public static void main(String[] args) {

}

如何选择单例创建方式

如果不需要延迟加载单例,可以使用枚举或者饿汉式,相对来说枚举性好于饿汉式。

如果需要延迟加载,可以使用静态内部类或者懒汉式,相对来说静态内部类好于懒韩式。

最好使用饿汉式

单例创建方式

(主要使用懒汉和懒汉式)

1.饿汉式:
类初始化时,会立即加载该对象,线程天生安全,调用效率高。

2.懒汉式:
类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象,具备懒加载功能。

3.静态内部方式:
结合了懒汉式和饿汉式各自的优点,真正需要对象的时候才会加载,加载类是线程安全的。

4.枚举单例:
使用枚举实现单例模式

优点: 实现简单、调用效率高,枚举本身就是单例, 由jvm从根本上提供保障!避免通过反射和反序列化的漏洞;

缺点: 没有延迟加载。

5.双重检测锁方式

因为JVM重排序、内存可见性的原因,可能会初始化多次,

所以: 需要通过 Double Check 双重检查+ synchronized + Volatile 解决 同步问题和可见性问题。

饿汉式

类初始化时,会立即加载该对象,线程天生安全,调用效率高。

package com.crazymakercircle.designmodel.singleton;
//饿汉式
public class FSingleton {

    // 类初始化时,会立即加载该对象,线程安全,调用效率高
    private static final FSingleton instance = new FSingleton();

    // 私有化构造方法
    private FSingleton() {
    }

 public   static FSingleton getInstance() {
        return instance;
    }


}

饿汉模式就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了。

特点:

  • 是否 Lazy 初始化:否
  • 是否多线程安全:是
  • 实现难度:易

优点:

  • 没有加锁,执行效率会提高。
  • 这种方式比较常用,但容易产生垃圾对象
  • 它基于JVM class loader 机制, 是单线程执行的, 避免了多线程的同步问题

缺点:

  • 类加载时就初始化,浪费内存

懒汉式

类初始化时,不会初始化该对象,

真正需要使用的时候,才会创建该对象,具备懒加载功能。

package com.crazymakercircle.designmodel.singleton;
//懒汉模式
public class FLazySingleton {

    //类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象。
    private static  FLazySingleton instance = null;

    // 私有化构造方法
    private FLazySingleton() {
    }

    //真正需要使用的时候才会创建该对象
    public static synchronized FLazySingleton getInstance() {
        if(null==instance)
        {
            instance=new FLazySingleton();
        }
        return instance;
    }


}

静态内部类

静态内部方式:

结合了懒汉式和饿汉式各自的优点,真正需要对象的时候才会加载,加载类是线程安全的。

package com.crazymakercircle.designmodel.singleton;

public class Singleton { 
    //静态内部类 
    private static class LazyHolder { 
          //通过final保障初始化时的线程安全  
           private static final Singleton INSTANCE = new Singleton(); 
    } 
       //私有的构造器 
    private Singleton (){} 
      //获取单例的方法 
    public static final Singleton getInstance() { 
      //返回内部类的静态、最终成员 
       return LazyHolder.INSTANCE; 
    } 
} 

枚举单例式

枚举单例:

使用枚举实现单例模式 优点:实现简单、调用效率高,

枚举本身就是单例,由jvm从根本上提供保障!避免通过反射和反序列化的漏洞, 缺点没有延迟加载。

package com.lijie;


package com.crazymakercircle.designmodel.singleton;
//饿汉式
public enum SingletonEnumStyle {
    INSTANCE;
    // 类初始化时,会立即加载该对象,线程安全,调用效率高

    public  static SingletonEnumStyle getInstance() {
        return INSTANCE;
    }

}

枚举实现单例模式 优点:

  • 实现简单、枚举本身就是单例,由jvm从根本上提供保障!
  • 避免通过反射和反序列化的漏洞

缺点:

  • 没有延迟加载

双重检测锁方式

所谓懒加载,就是直到第一次被调用时才加载。其实现需要考虑并发问题和指令重排,代码如下:

public class Singleton {
 
    private volatile static Singleton instance; //①
 
    private Singleton() { //②
    }
 
    public static Singleton getInstance() {
        if (instance == null) {//③
            synchronized (Singleton.class) {
                if (instance == null) {//④
                    instance = new Singleton();//⑤
                }
            }
        }
        return instance;
    }
}

这段代码精简至极,没有一个字符是多余的,下面逐行解读一下:

首先,注意到①处的volatile关键字,它具备两项特性:

一是保证此变量对于所有线程的可见性。

即当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。

二是禁止指令重排序优化。

这里解释一下指令重排序优化:

代码 ⑤ 处的instance = new Singleton(); 并不是原子的,大体可分为如下 3 步:

  1. 分配内存
  2. 调用构造函数初始化成实例
  3. 让instance指向分配的内存空间

JVM 允许在保证结果正确的前提下进行指令重排序优化。

即如上 3 步可能的顺序为1->2->3 或 1->3->2 。

如果顺序是 1->3->2 ,当 3 执行完,2 还未执行时,另一个线程执行到代码 ③ 处,发现instance不为null,直接返回还未初始化好的instance并使用,就会报错。

所以使用volatile,就是为了保证线程间的可见性和防止指令重排。

其次,代码②处将构造函数声明为private目的:在于阻止使用new Singleton()这样的代码生成新实例。

最后,当客户端调用Singleton.getInstance()时,先检查是否已经实例化(代码③),未实例化时同步代码块,然后再次检查是否已实例化(代码④),然后才执行代码⑤。

两次检查的意义在于,防止synchronized同步过程中其他线程进行了实例化。

这就是著名的双重检查锁(Double check lock)实现单例,也即懒加载。

网上也有直接对 getInstance()方法加锁的版本,这样大范围的方法级别加锁会导致并发变低,实际上第一次调用生成实例之后,后续获取实例根本不需要并发控制了。

而本例的双重检查锁版本可以避免此并发问题。

线程安全的单例模式

答案是:枚举单例

Caffeine 源码中,如何使用单例模式的

并且,单例的名称叫做 INSTANCE

通过这个 INSTANCE 名字 做 关键词搜索, 能搜到一大把

Skywalking 源码中,如何使用单例模式

答案是:枚举单例

并且,单例的名称叫做 INSTANCE

单例模式懒汉式和饿汉式有哪些区别

单例模式是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。

明确定义后,看一下代码:

饿汉模式

package com.crazymakercircle.designmodel.singleton;
//饿汉式
public class FSingleton {

    // 类初始化时,会立即加载该对象,线程安全,调用效率高
    private static final FSingleton instance = new FSingleton();

    // 私有化构造方法
    private FSingleton() {
    }

 public   static FSingleton getInstance() {
        return instance;
    }


}

饿汉模式就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了。

特点:

  • 是否 Lazy 初始化:否
  • 是否多线程安全:是
  • 实现难度:易

优点:

  • 没有加锁,执行效率会提高。
  • 这种方式比较常用,但容易产生垃圾对象
  • 它基于JVM class loader 机制, 是单线程执行的, 避免了多线程的同步问题

缺点:

  • 类加载时就初始化,浪费内存,

懒汉模式

public class Singleton {
 
    private volatile static Singleton instance; //①
 
    private Singleton() { //②
    }
 
    public static Singleton getInstance() {
        if (instance == null) {//③
            synchronized (Singleton.class) {
                if (instance == null) {//④
                    instance = new Singleton();//⑤
                }
            }
        }
        return instance;
    }
}

而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。

特点:

  • 是否 Lazy 初始化:是
  • 是否多线程安全:是
  • 实现难度:难

1、线程安全:

饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,

懒汉式本身是非线程安全的,需要通过多种手段,保证线程安全和内存可见性:

  • volatile 保证内存可见性
  • synchronized + 双重检查 保证线程安全

2、资源加载和性能:

饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成。

而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。

  • 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  • 主要解决:一个全局使用的类频繁地创建与销毁。
  • 何时使用:当您想控制实例数目,节省系统资源的时候。
  • 如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
  • 关键代码:构造函数是私有的。
  • 应用实例:1、一个党只能有一个主席。
    2、Windows是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
    3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。

优点:

1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
2、避免对资源的多重占用(比如写文件操作)。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

使用场景:

1、要求生产唯一序列号。
2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

注意事项:getInstance() 方法中需要使用 Double Check 双重检查锁,synchronized (Singleton.class) 防止多线程同时进入造成instance 被多次实例化。

如本文“对您有用”,欢迎随意打赏作者,让我们坚持创作!

1 打赏
Enamiĝu al vi
不要为明天忧虑.因为明天自有明天的忧虑.一天的难处一天当就够了。
543文章 68评论 295点赞 599604浏览

随机文章
Spring笔记14—整合MyBatis
5年前
Java—ArrayList源码走一波
5年前
Java—并发编程—重入锁
4年前
Java—ASM字节码编程
3年前
Redis—注解接口限流
2年前
博客统计
  • 日志总数:543 篇
  • 评论数目:68 条
  • 建站日期:2020-03-06
  • 运行天数:1936 天
  • 标签总数:23 个
  • 最后更新:2024-12-20
Copyright © 2025 网站备案号: 浙ICP备20017730号 身体没有灵魂是死的,信心没有行为也是死的。
主页
页面
  • 归档
  • 摘要
  • 杂图
  • 问题随笔
博主
Enamiĝu al vi
Enamiĝu al vi 管理员
To be, or not to be
543 文章 68 评论 599604 浏览
测试
测试
看板娘
赞赏作者

请通过微信、支付宝 APP 扫一扫

感谢您对作者的支持!

 支付宝 微信支付