- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
本文首发公众号:小码A梦 。
单例模式是设计模式中最简单一个设计模式,该模式属于创建型模式,它提供了一种创建实例的最佳方式.
单例模式的定义也比较简单:一个类只能允许创建一个对象或者实例,那么这个类就是单例类,这种设计模式就叫做单例模式.
单例模式有哪些好处:
单例模式,其中的 "例" 表示 "实例" ,一个类需要保证仅有一个实例,并提供一个访问它的全局访问点,实现一个单例,需要符合以下几点要求:
在电商系统的订单模块,每次下单都需要生成新的订单号。就需要调用订单号生成器.
1、 创建一个简单单例类 。
public class SnGenerator {
private AtomicLong id = new AtomicLong(0);
// 创建一个 Singleton 对象
private static SnGenerator instance = new SnGenerator();
// 构造函数设置为 private,类就无法被实例化
private SnGenerator() {}
// 获取唯一实例
public static SnGenerator getInstance() {
return instance;
}
public long getSn() {
return id.incrementAndGet();
}
}
2、获取 Singleton 类的唯一实例 。
public class SingletonTest {
public static void main(String[] args) {
// 编译报错,因为 Singleton 构造函数是私有的
//Singleton singleton = new Singleton();
SnGenerator snGenerator = SnGenerator.getInstance();
for (int i = 0; i < 10; i++) {
System.out.println(snGenerator.getSn());
}
}
}
控制台输出生成的 id:
1
2
3
4
5
6
7
8
9
10
以上首先创建一个单例类,提供唯一的单例获取方法 getInstance。SingletonTest 类通过 Singleton.getInstance 获取实例,获取到实例,也就获取到实例所有的方法。示例中调用 getSn 方法,获取到唯一的订单号了.
饿汉单例实现起来比较简单, 所谓 "饿汉" 重点在饿,开始就需要创建单例 。在类加载时,就创建好了实例。所以 instance 实例的创建是线程安全,不存在线程安全问题。但是这种方式不支持延迟加载,类加载时占用的内存就比较高.
public class SnGenerator {
private AtomicLong id = new AtomicLong(0);
private static SnGenerator instance = new SnGenerator();
private SnGenerator() {}
public static SnGenerator getInstance() {
return instance;
}
public long getSn() {
return id.incrementAndGet();
}
}
饿汉单例解决线程安全问题,项目启动时就创建好了实例,就需要考虑创建和获取实例的线程安全问题。但是不支持延迟,如果实例的占用内存比较大,或者实例加载时间比较长,类加载的时候就创建实例,就比较浪费内存或者增加项目启动时间.
对饿汉单例来说,不支持延迟加载,确实是比较浪费内存。但是一个实例内存相对于一个 Java 项目内存占用影响是微乎其微。部署服务端项目时会分配几倍于项目启动占用的内存,所以饿汉单例占用内存还是可以接受的。而且如果占用内存比较大,初始化实例也可以发现内存不足的问题,并及时的处理。避免程序运行后,再去初始化实例,导致系统内存溢出,影响系统稳定性.
既然饿汉单例单例不支持延迟加载,那我们就介绍一下支持延迟的加载的单例:懒汉单例。 所谓"懒汉"重点在懒,一开始是不会初始化实例,而等到被调用才会初始化单例 .
public class LazySnGenerator {
private AtomicLong id = new AtomicLong(0);
private static LazySnGenerator instance;
// 构造函数设置为 private,类就无法被实例化
private LazySnGenerator() {}
// 获取唯一实例
public static LazySnGenerator getInstance() {
if (instance == null) {
instance = new LazySnGenerator();
}
return instance;
}
public long getSn() {
return id.incrementAndGet();
}
}
上面的懒汉单例最开始不会初始化实例,而且等到 getInstance 方法被调用时,才会时候实例,这样支持懒加载的方式,优点是不占内存.
但是懒汉单例缺点也比较明显,在多线程环境下,getInstance 方法不是线程安全的.
打个比方,多个线程同时执行到 if (instance == null) 结果都为 true,进而都会创建实例,所以上面的懒汉单例不是线程安全的实例.
懒汉单例存在多线程安全问题,第一想到的就是给 getInstance 添加同步锁,添加锁后,保证了线程的安全.
public class LazySnGenerator {
private AtomicLong id = new AtomicLong(0);
private static LazySnGenerator instance;
// 构造函数设置为 private,类就无法被实例化
private LazySnGenerator() {}
// 获取唯一实例
public synchronized static LazySnGenerator getInstance() {
if (instance == null) {
instance = new LazySnGenerator();
}
return instance;
}
public long getSn() {
return id.incrementAndGet();
}
}
添加同步锁后懒汉单例,并发量下降,如果方法被频繁使用,频繁的加锁、释放锁,有很大的性能瓶颈.
饿汉单例不支持延迟加载,懒汉单例有性能问题,不支持高并发。就需要一种既支持延迟加载又支持高并发的单例,也就是双重检验懒汉单例。对上面的懒汉单例进行优化之后,得出如下代码.
public class LazyDoubleCheckSnGenerator {
private AtomicLong id = new AtomicLong(0);
private static LazyDoubleCheckSnGenerator instance;
// 构造函数设置为 private,类就无法被实例化
private LazyDoubleCheckSnGenerator() {}
// 双重检测
public static LazyDoubleCheckSnGenerator getInstance() {
if (instance == null) {
// 类级别锁
synchronized (LazyDoubleCheckSnGenerator.class) {
if (instance == null) {
instance = new LazyDoubleCheckSnGenerator();
}
}
}
return instance;
}
public long getSn() {
return id.incrementAndGet();
}
}
双重检测首先判断实例是否为空,如果为空就使用类级别锁锁住整个类,其他线程也只能等待实例新建后,才能执行 synchronized 代码块的代码,而此时 instance 不为空,就不会继续新建实例。从而确保线程安全。getInstance 只会在最开始的时候,性能较差。创建实例之后,后面的线程都不会请求到 synchronized 代码块。后续并发性能也提高了.
CPU 指令重排可能会导致新建对象并赋值给 instance 之后,还来得及初始化,就会其他线程使用。导致系统报错,为了解决这个问题,就需要给 instance 成员变量添加 volatile 关键字禁止指令重排.
和双重检测单例一样,静态内部类既支持延迟加载又支持高并发。首先看一下代码实现.
public class SnStaticClass {
private AtomicLong id = new AtomicLong(0);
private static LazySnGenerator instance;
// 构造函数设置为 private,类就无法被实例化
private SnStaticClass() {}
private static class SingletonHolder{
private static final SnStaticClass instance = new SnStaticClass();
}
// 静态内部类获取实例
public synchronized static SnStaticClass getInstance() {
return SingletonHolder.instance;
}
public long getSn() {
return id.incrementAndGet();
}
}
SingletonHolder 是一个静态内部类,当 SnStaticClass 加载时,并不会加载 SingletonHolder 静态内部类,也就不会执行静态内部的代码。在 类加载初始化阶段,不会执行静态内部类的代码 。只有 getInstance 方法,执行 SingletonHolder 静态内部类,才会创建 SnStaticClass 实例。而 instance 创建的安全性,都是由 JVM 保证的。虚拟机使用加锁同步机制,保证实例只会创建一次。这种方式不仅 实现延迟加载,也保证线程安全 .
枚举实例单例是一个简答实现方式,这种方式是通过 Java 枚举类性本身的特性,来保证实例的唯一和线程的安全.
public enum SnGeneratorEnum {
instance;
private AtomicLong id = new AtomicLong(0);
public long getSn() {
return id.incrementAndGet();
}
}
在 Java 开发中,有很多地方使用到了单例模式。比如 JDK、Spring.
Runtime 类封装了 Java 运行信息,可以获取有关运行时环境的信息,每个 JVM 进程只有一个运行环境,只需要一个 Runtime 实例,所以 Runtime 一个单例实现.
public class Runtime {
private static Runtime currentRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
......省略
}
由以上代码可知,Runtime 是一个饿汉单例,类加载时就初始化了实例,提供 getRuntime 方法提交单例的调用.
大部分 Java 项目都是基于 Spring 框架开发的,Spring 中 bean 简单分成单例和多例,其中 bean 的单例实现既不是饿汉单例也不是懒汉单例。是基于 单例注册表 实现,注册表就就是一个哈希表,使用一个哈希表存储 bean 的信息.
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
singletonObjects 表示一个单例注册表,key 存储 bean 的名称,value 存储 bean 的实例信息。DefaultSingletonBeanRegistry 类的 getSingleton 方法实现 bean 单例,以下摘取主要的的代码.
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
// 锁住注册表
synchronized (this.singletonObjects) {
// 获取 bean 信息,不存在就创建一个 bean
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
try {
// 创建 bean
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
}
catch (BeanCreationException ex) {
}
finally {
}
// 创建好的 bean 存进 map 中
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
Spring 获取 bean,锁住整个注册表,首先从 map 中获取 bean,如果 bean 不存在,就创建一个 bean,并存入 map 中。后续获取 bean,获取到的都是 map 的 bean。并不会创建新的 bean.
单例模式一个最简单的一种设计模式,该设计模式是一种创建型设计模式。规定了一个类只能创建一实例。很多类只需要一个实例,这样的好处,减少内存的占用和 CPU 的开销,减少 GC 的次数。同时也减少对资源的重复使用.
单例模式(上):为什么说支持懒加载的双重检测不比饿汉式更优?
Spring学习之路——单例模式和多例模式 。
最后此篇关于Java设计模式实战系列—单例模式的文章就讲到这里了,如果你想了解更多关于Java设计模式实战系列—单例模式的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
问题情景 混淆群内的小伙伴遇到这么个问题,Mailivery 这个网站登录后,明明提交的表单(邮箱和密码也正确)、请求头等等都没问题,为啥一直重定向到登录页面呢?唉,该出手时就出手啊,我也看看咋回事
实战-行业攻防应急响应 简介: 服务器场景操作系统 Ubuntu 服务器账号密码:root/security123 分析流量包在/home/security/security.pcap 相
背景 最近公司将我们之前使用的链路工具切换为了 OpenTelemetry. 我们的技术栈是: OTLP C
一 同一类的方法都用 synchronized 修饰 1 代码 package concurrent; import java.util.concurrent.TimeUnit; public c
一 简单例子 1 代码 package concurrent.threadlocal; /** * ThreadLocal测试 * * @author cakin */ public class T
1. 问题背景 问题发生在快递分拣的流程中,我尽可能将业务背景简化,让大家只关注并发问题本身。 分拣业务针对每个快递包裹都会生成一个任务,我们称它为 task。task 中有两个字段需要
实战环境 elastic search 8.5.0 + kibna 8.5.0 + springboot 3.0.2 + spring data elasticsearch 5.0.2 +
Win10下yolov8 tensorrt模型加速部署【实战】 TensorRT-Alpha 基于tensorrt+cuda c++实现模型end2end的gpu加速,支持win10、
yolov8 tensorrt模型加速部署【实战】 TensorRT-Alpha 基于tensorrt+cuda c++实现模型end2end的gpu加速,支持win10、linux,
目录如下: 为什么需要自定义授权类型? 前面介绍OAuth2.0的基础知识点时介绍过支持的4种授权类型,分别如下: 授权码模式 简化模式 客户端模式 密码模式
今天这篇文章介绍一下如何在修改密码、修改权限、注销等场景下使JWT失效。 文章的目录如下: 解决方案 JWT最大的一个优势在于它是无状态的,自身包含了认证鉴权所需要的所有信息,服务器端
前言 大家好,我是捡田螺的小男孩。(求个星标置顶) 我们日常做分页需求时,一般会用limit实现,但是当偏移量特别大的时候,查询效率就变得低下。本文将分四个方案,讨论如何优化MySQL百万数
前言 大家好,我是捡田螺的小男孩。 平时我们写代码呢,多数情况都是流水线式写代码,基本就可以实现业务逻辑了。如何在写代码中找到乐趣呢,我觉得,最好的方式就是:使用设计模式优化自己
我们先讲一些arm汇编的基础知识。(我们以armv7为例,最新iphone5s上的64位暂不讨论) 基础知识部分: 首先你介绍一下寄存器: r0-r3:用于函数参数及返回值的传递 r4-r6
一 同一类的静态方法都用 synchronized 修饰 1 代码 package concurrent; import java.util.concurrent.TimeUnit; public
DRF快速写五个接口,比你用手也快··· 实战-DRF快速写接口 开发环境 Python3.6 Pycharm专业版2021.2.3 Sqlite3 Django 2.2 djangorestfram
一 添加依赖 org.apache.thrift libthrift 0.11.0 二 编写 IDL 通过 IDL(.thrift 文件)定义数据结构、异常和接口等数据,供各种编程语言使用 nam
我正在阅读 Redis in action e-book关于semaphores的章节.这是使用redis实现信号量的python代码 def acquire_semaphore(conn, semn
自定义控件在WPF开发中是很常见的,有时候某些控件需要契合业务或者美化统一样式,这时候就需要对控件做出一些改造。 目录 按钮设置圆角
师父布置的任务,让我写一个服务练练手,搞清楚socket的原理和过程后跑了一个小demo,很有成就感,代码内容也比较清晰易懂,很有教育启发意义。 代码 ?
我是一名优秀的程序员,十分优秀!