在系统开发设计中,总会存在这么几种情况,①需要频繁创建销毁的对象,②创建对象需要消耗很多资源,但又经常用到的对象(如工具类对象,频繁访问数据库或文件的对象,数据源,session工厂等);③某个类只能有一个对象,如应用中的Application类;这时就应该考虑使用单例模式。个人博客地址www.mycookies.cn
确保类只有一个实例,并提供全局访问点。
在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。
/**
* 饿汉式[工厂方法]
*
* @author Jann Lee
* @date 2019-07-21 14:28
*/
public class Singleton1 {
private static final Singleton1 instance = new Singleton1();
private Singleton1() {
}
public static Singleton1 getInstance() {
return instance;
}
}
/**
* 饿汉式[公有域]
*/
public class Singleton2 {
public static final Singleton2 instance = new Singleton2();
private Singleton2() {
}
}
/**
* 饿汉式[静态代码块,工厂方法]
*/
public class Singleton3 {
private static Singleton3 instance;
static {
instance = new Singleton3();
}
private Singleton3() {
}
public static Singleton3 getInstance() {
return instance;
}
}
以上三种写法仅仅是在代码实现上的差异,是“饿汉式”最常见的实现。
优点:类装在时候完成实例化,避免了多线程问题
缺点:可能造成内存浪费(可能实例从来没有被使用到)
顾名思义,因为懒,所以在用到时才会进行实例化。
实现思路:私有化构造方法-> 声明成员变量 -> 提供公共方法访问【如果成员变量不为空,直接返回,如果为空创建后返回】
/**
* 1.懒汉式[非线程安全]
*
* @author Jann Lee
* @date 2019-07-21 14:31
**/
public class Singleton1 {
private static Singleton1 instance;
private Singleton1() {
}
public static Singleton1 getInstance() {
if (instance == null) {
instance = new Singleton1();
}
return instance;
}
}
上述实现方式是最容易被想到的,但是应该也算是一种错误的实现方式,因为再多线程环境下一个对象可能被创建了多次。
为了解决线程安全问题, 将方法进行同步
/**
* 2.懒汉式[同步方法]
**/
public class Singleton2 {
private static Singleton2 instance;
private Singleton2() {
}
/**
* 同步方法,保证线程安全
*/
public static synchronized Singleton2 getInstance() {
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
}
的确,这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步,然而对象创建只需要进行一次即可;因为并发读取数据不需要进行同步,所以这种在方法进行同步是没有必要的,在并发情况下肯定会带来性能上的损失。
这时我们首先想到的时缩小同步范围,只有在创建对象的时候使用同步,即使用同步代码块实现。
/**
* 4.懒汉式[同步代码块]
**/
public class Singleton3 {
private static Singleton3 instance;
private Singleton3() {
}
/**
* 同步代码块,并不能保证线程安全
*/
public static Singleton3 getInstance() {
if (instance == null) {
synchronized (Singleton3.class) {
instance = new Singleton3();
}
}
return instance;
}
}
这是一种错误的实现方式,虽然减少了加锁范围,但是又回到了并发环境下的重复创建对象的问题,具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争,线程1获取到了锁,线程2挂起,执行完毕后释放锁,此时线程二获取到锁后回接着往下执行,再次创建对象。
解决上述问题,我们只需要在同步代码块中加入校验。
/**
* 懒汉式[双重检查锁]
**/
public class Singleton4 {
private static volatile Singleton4 instance;
private Singleton4() {
}
public static Singleton4 getInstance() {
if (instance == null) {
synchronized (Singleton4.class) {
if (instance == null) {
instance = new Singleton4();
}
}
}
return instance;
}
}
注意:此处除了再次校验是否为空之外,还给成员变量之前加上了一个volatile关键字,这个非常重要。因为在代码执行过程中会发生指令重排序。这里你只需要知道加上volatile之后能保证指令不会被重排序,程序能正确执行,而不加则可能不出错。
在编译器和处理器为了提高程序的运行性能,会对指令进行重新排序。即代码会被编译成操作指令,而指令执行顺序可能会发生变化。
java中创建对象分为 三个步骤【可以简单理解为三条指令】
- 分配内存,内存空间初始化
- 对象初始化,类的元数据信息,hashCode等信息
- 将内存地址返回
如果2,3顺序发生了变化,另一个线程获得锁时恰好还没有完成对象初始化,即instance指向null,就会重复创建对象。
在静态内部类中持有一个对象。
/**
* 使用静态内部类实现单例模式
*
* @author Jann Lee
* @date 2019-07-21 14:47
**/
public class Singleton {
private Singleton (){}
public static Singleton getInstance() {
return ClassHolder.singleton;
}
/**
* Singleton装载完成后,不会创建对象
* 调用getInstance时候,静态内部类ClassHolder才进行装载
*/
private static class ClassHolder {
private static final Singleton singleton = new Singleton();
}
}
静态内部类的实现则是根据java语言特性实现的,即让静态内部类持有一个对象;根据类加载机机制的特点,每个类只会加载一次,并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次,无论是否在多线程环境中。
在java中枚举也是类的一种,实际上枚举的每一个元素都是一个枚举类的对象,可以理解为对类的一种封装,默认私有构造方法,且不能使用public修饰。
/**
* 枚举实现单例模式
*
* 为了便于理解给枚举类添加了两个属性
* @author Jann Lee
* @date 2019-07-21 14:55
**/
public enum Singleton {
INSTANCE;
private String name;
private int age;
Singleton04() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
标签: 设计模式
好文要顶;) 关注我;) 收藏该文;) ; "分享至新浪微博") ; "分享至微信")
+加关注;)
0
0
« 上一篇: 浅谈Java中的深克隆和浅克隆(阿里面试)
» 下一篇: 并发编程-你真的知道并发问题产生的源头吗?
Original url: Access
Created at: 2019-10-27 22:47:37
Category: default
Tags: none
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
java windows火焰图_mob64ca12ec8020的技术博客_51CTO博客 - 在windows下不可行,不知道作者是怎样搞的 监听SpringBoot 服务启动成功事件并打印信息_监听springboot启动完毕-CSDN博客 SpringBoot中就绪探针和存活探针_management.endpoint.health.probes.enabled-CSDN博客 u2u转换板 - 嘉立创EDA开源硬件平台 Spring Boot 项目的轻量级 HTTP 客户端 retrofit 框架,快来试试它!_Java精选-CSDN博客 手把手教你打造一套最牛的知识笔记管理系统! - 知乎 - 想法有重合-理论可参考 安宇雨 闲鱼 机械键盘 客制化 开贴记录 文本 linux 使用find命令查找包含某字符串的文件_beijihukk的博客-CSDN博客_find 查找字符串 ---- mac 也适用 安宇雨 打字音 记录集合 B站 bilibili 自行搭建 开坑 真正的客制化 安宇雨 黑苹果开坑 查找工具包maven pom 引用地 工具网站 Dantelis 介绍的玩轴入坑攻略 --- 关于轴的一些说法 --- 非官方 ---- 心得而已 --- 长期开坑更新 [本人问题][新开坑位]关于自动化测试的工具与平台应用 机械键盘 开团 网站记录 -- 能做一个收集的程序就好了 不过现在没时间 -- 信息大多是在群里发的 - 你要让垃圾佬 都去一个地方看难度也是很大的 精神支柱 [超级前台]sprinbboot maven superdesk-app 记录 [信息有用] [环境准备] [基本完成] [sebp/elk] 给已创建的Docker容器增加新的端口映射 - qq_30599553的博客 - CSDN博客 [正在研究] Elasticsearch, Logstash, Kibana (ELK) Docker image documentation elasticsearch centos 安装记录 及 启动手记 正式服务器 39 elasticsearch 问题合集 不断更新 6.1.1 | 6.5.1 两个版本 博客程序 - 测试 - bug记录 等等问题 laravel的启动过程解析 - lpfuture - 博客园 OAuth2 Server PHP 用 Laravel 搭建带 OAuth2 验证的 RESTful 服务 | Laravel China 社区 - 高品质的 Laravel 和 PHP 开发者社区 利用Laravel 搭建oauth2 API接口 附 Unauthenticated 解决办法 - 煮茶的博客 - SegmentFault 思否 使用 OAuth2-Server-php 搭建 OAuth2 Server - 午时的海 - 博客园 基于PHP构建OAuth 2.0 服务端 认证平台 - Endv - 博客园 Laravel 的 Artisan 命令行工具 Laravel 的文件系统和云存储功能集成 浅谈Chromium中的设计模式--终--Observer模式 浅谈Chromium中的设计模式--二--pre/post和Delegate模式 浅谈Chromium中的设计模式--一--Chromium中模块分层和进程模型 DeepMind 4 Hacking Yourself README.md update 20211011
Laravel China 简书 知乎 博客园 CSDN博客 开源中国 Go Further Ryan是菜鸟 | LNMP技术栈笔记 云栖社区-阿里云 Netflix技术博客 Techie Delight Linkedin技术博客 Dropbox技术博客 Facebook技术博客 淘宝中间件团队 美团技术博客 360技术博客 古巷博客 - 一个专注于分享的不正常博客 软件测试知识传播 - 测试窝 有赞技术团队 阮一峰 语雀 静觅丨崔庆才的个人博客 软件测试从业者综合能力提升 - isTester IBM Java 开发 使用开放 Java 生态系统开发现代应用程序 pengdai 一个强大的博主 HTML5资源教程 | 分享HTML5开发资源和开发教程 蘑菇博客 - 专注于技术分享的博客平台 个人博客-leapMie 流星007 CSDN博客 - 舍其小伙伴 稀土掘金 Go 技术论坛 | Golang / Go 语言中国知识社区
最新评论