【设计模式-2.1】创建型——单例模式

说明:设计模式根据用途分为创建型、结构性和行为型。创建型模式主要用于描述如何创建对象,本文介绍创建型中的单例模式。

饿汉式单例

单例模式是比较常见的一种设计模式,旨在确保对象的唯一性,什么时候去使用这个对象都是同一个。这样设计的目的是为了避免对象重复创建,浪费资源,同时也保证了对象的唯一性,不至于多个相同的对象,状态不一致的情况。

以下是单例模式的简单实现:

/*** 太阳类*/
public class Sun {private static final Sun sun = new Sun();private Sun() {}public static Sun getInstance() {return sun;}
}

需要注意,对象的创建是在对象类内部,且为static final修饰,并且私有化构造方法,使其不能在外部被创建,其次创建一个静态方法,返回其对象

此时,外部通过类名.方法名的方式,可以访问到该对象,且始终为同一个;

public class Test {public static void main(String[] args) {Sun sun1 = Sun.getInstance();Sun sun2 = Sun.getInstance();System.out.println(sun1 == sun2);}
}

两次获取到的对象相同

在这里插入图片描述

上面这种创建方式,称为饿汉式单例(Eager Singleton),即类加载完成时就创建对象

懒汉式单例

以下这种方式,称为懒汉式单例(Lazy Singleton),在调用静态方法时才创建对象

/*** 懒汉式单例模式*/
public class LazySun {private static LazySun sun = null;private LazySun() {}public static LazySun getInstance() {if (sun == null) {sun = new LazySun();}return sun;}
}

这种创建方式,可以实现延迟加载,节约资源。但是在多线程并发时,如果有多个线程在if (sun == null),就会导致对象被创建多次,所以我们很容易想到的是加锁,将静态方法加上锁,如下:

/*** 懒汉式单例模式*/
public class LazySun {private static LazySun sun = null;private LazySun() {}public synchronized static LazySun getInstance() {if (sun == null) {sun = new LazySun();}return sun;}
}

但是,给方法加上锁后,会影响性能,可以考虑减少锁的范围,只锁住创建对象这一行,如下:

/*** 懒汉式单例模式*/
public class LazySun {private static LazySun sun = null;private LazySun() {}public static LazySun getInstance() {if (sun == null) {synchronized (LazySun.class) {sun = new LazySun();}}return sun;}
}

双重检查锁定

但是,还有问题。在多线程并发下,如果线程A在创建对象(未完成),线程B、C在if (sun == null) 这里判断为true,即便对象创建完成,线程B、C通过了if判断,也还是会依次再创建对象,也造成了对象被重复创建。因此,还需要改造,如下:

/*** 双重检查锁定*/
public class LazySun {private volatile static LazySun sun = null;private LazySun() {}public static LazySun getInstance() {// 第一次判断if (sun == null) {synchronized (LazySun.class) {// 第二次判断if (sun == null){sun = new LazySun();}}}return sun;}
}

就是在锁住的代码块里面再进行一次非空判断,称为双重检查锁定(Double-Check Locking),同时,单例对象修饰符加上volatile,确保多个线程都能正确处理;

IoDH

饿汉式单例,是在类被加载时就创建了对象。不需要考虑多线程,但是无论是否使用到对象都创建,造成了资源浪费。而懒汉式单例,虽然做到了延迟加载,但是需要处理好多线程情况下的对象创建,使用了锁,影响了性能。

那有没有一种更好了方式,既能在多线程下使用,又不会影响性能?

在《设计模式的艺术》(刘伟著)中作者提供了一种更好的创建方式,称为IoDH(Initialization on Demand Holder,按需初始化),代码如下:

/*** IoDH*/
public class Singleton {private Singleton() {}private static class HolderClass {private final static Singleton instance = new Singleton();}public static Singleton getInstance() {return HolderClass.instance;}
}

这种方式是对饿汉式单例的改进,是在单例类里创建了一个内部类,将单例对象的创建放在了这个内部类里面。因为单例对象不是单例类的一个成员变量,所以对象在类加载时不会被创建,而是会在调用静态方法时被创建,这样既能延迟加载,又没有使用锁,影响性能,一举两得。

书中说,通过使用IoDH,既可以实现延迟加载,又可以保证线程安全,不影响系统性能。因此,IoDH不失为一种最好的Java语言单例模式实现方式;其缺点是与编程语言本身的特性相关,很多面向对象语言不支持IoDH。

总结

本文参考《设计模式的艺术》、《秒懂设计模式》两书

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/218584.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第一百七十九回 自定义SlideImageSwitch

文章目录 1. 概念介绍2. 思路与方法2.1 实现思路 3. 代码与效果3.1 示例代码3.2 运行效果 4. 内容总结 我们在上一章回中介绍了"SlideSwitch组件"相关的内容,本章回中将介绍自定义SlideImageSwitch.闲话休提,让我们一起Talk Flutter吧。 1. 概…

代码随想录算法训练营 ---第四十二天

今天开始学习 动态规划:背包问题 也是比较难的一部分了 动态规划:背包问题 理论基础 01背包(二维数组) 有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用…

网络运维与网络安全 学习笔记2023.11.25

网络运维与网络安全 学习笔记 第二十六天 今日目标 ACL原理与类型、基本ACL配置、高级ACL配置 高级ACL之ICMP、高级ACL之telnet ACL原理与类型 项目背景 为了企业的业务安全,要求不同部门对服务器有不同的权限 PC1不能访问Server PC2允许访问Server 允许其他所…

求集合的笛卡尔乘积

求集合的笛卡尔乘积 一:【实验目的】二:【实验内容】三:【实验原理】四:代码实现: 一:【实验目的】 通过编实现给定集合A和B的笛卡尔积CAA,DAB,EBA,FAAB,GA(A*B). 二:【实验内容】…

【Android Gradle】之Gradle入门及 wrapper 生成(一)

😄作者简介: 小曾同学.com,一个致力于测试开发的博主⛽️,主要职责:测试开发、CI/CD 如果文章知识点有错误的地方,还请大家指正,让我们一起学习,一起进步。 😊 座右铭:不…

illuminate/database 使用 四

文档:Hyperf Database: Getting Started - Laravel 10.x - The PHP Framework For Web Artisans 因为hyperf使用illuminate/database,所以按照文章,看illuminate/database代码实现。 一、读写分离 根据文档读写的host可以分开。设置读写分…

系列十六、Spring IOC容器的扩展点

一、概述 Spring IOC容器的扩展点是指在IOC加载的过程中,如何对即将要创建的bean进行扩展。 二、扩展点 2.1、BeanDefinitionRegistryPostProcessor 2.1.1、概述 BeanDefinitionRegistryPostProcessor是bean定义的后置处理器,在BeanDefinition加载后&a…

142.【Nginx负载均衡-01】

Nginx_基础篇 (一)、Nginx 简介1.背景介绍(1).http和三大邮局协议(2).反向代理与正向代理 2.常见服务器对比(1).公司介绍(2).lls 服务器(3).Tomcat 服务器(4).Apache 服务器(5).Lighttpd 服务器(6).其他的服务器 3.Nginx的优点(1).速度更快、并发更高(2).配置简单,扩…

力扣:提莫攻击

代码&#xff1a; class Solution { public:int findPoisonedDuration(vector<int>& timeSeries, int duration){//根据数组中给出的元素的值来进行判断&#xff01;//若后面元素-前面元素>d 中了d秒&#xff01;// <d 中了差的秒数&…

从0到0.01入门 Webpack| 008.精选 Webpack面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

常见位运算的详讲!

今日为大家详细讲解一番关于常见位运算的操作&#xff0c;本文主要介绍一些位运算的操作符&#xff0c;然后再通过简单->中等->困难的例题&#xff0c;让大家彻底搞懂关于位运算的知识&#xff01; 位运算的介绍&#xff01; 1.基础位运算 ">>"右移操作…

WebGL/threeJS面试题扫描与总结

什么是 WebGL&#xff1f;什么是 Three.js&#xff1f;请解释three.js中的WebGL和Canvas的区别&#xff1f; WebGL(全写Web Graphics Library)是一种3D绘图协议&#xff0c;这种绘图技术标准允许把JavaScript和OpenGL ES 2.0结合在一起&#xff0c;通过增加OpenGL ES 2.0的一个…