创造型模式——单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

一、介绍

概述:保证一个类仅有一个实例,并提供一个访问它的全局访问点

适用场景:当您想控制实例数目,节省系统资源的时候。

实现方式:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

优点:

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

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

使用场景范例:

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

PS:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化

二、范例

我们将创建一个 SingleObject 类。SingleObject 类有它的私有构造函数和本身的一个静态实例。

SingleObject 类提供了一个静态方法,供外界获取它的静态实例。SingletonPatternDemo 类使用 SingleObject 类来获取 SingleObject 对象。

单例模式的 UML 图

步骤 1

创建一个 Singleton 类。

SingleObject.java

public class SingleObject {//创建 SingleObject 的一个对象private static SingleObject instance = new SingleObject(); //让构造函数为 private,这样该类就不会被实例化private SingleObject() {} //获取唯一可用的对象public static SingleObject getInstance() {return instance;}public void showMessage() {System.out.println("Hello World!");}
}

步骤 2

从 singleton 类获取唯一的对象。

SingletonPatternDemo.java

public class SingletonPatternDemo {public static void main(String[] args) {//不合法的构造函数//编译时错误:构造函数 SingleObject() 是不可见的//SingleObject object = new SingleObject();//获取唯一可用的对象SingleObject object = SingleObject.getInstance();//显示消息object.showMessage();}
}

步骤 3

执行程序,输出结果:

Hello World!

单例模式的几种实现方式

单例模式的实现有多种方式,如下所示:

1、懒汉式,线程不安全

是否 Lazy 初始化:是

是否多线程安全:否

实现难度:易

**描述:**这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

实例

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

接下来介绍的几种实现方式都支持多线程,但是在性能上有所差异。

2、懒汉式,线程安全

是否 Lazy 初始化:是

是否多线程安全:是

实现难度:易

描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。

实例

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

3、饿汉式

是否 Lazy 初始化:否

是否多线程安全:**是

实现难度:易

**描述:**这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。

实例

public class Singleton {private static Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}
}

4、双检锁/双重校验锁(DCL,即 double-checked locking)

JDK 版本:JDK1.5 起

是否 Lazy 初始化:**是

是否多线程安全:是

实现难度:较复杂

**描述:**这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
getInstance() 的性能对应用程序很关键。

实例

public class Singleton {private static volatile Singleton singleton;private Singleton() {}public static Singleton getSingleton() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}
}

5、登记式/静态内部类

是否 Lazy 初始化:是

是否多线程安全:是

实现难度:一般

**描述:**这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。

实例

public class Singleton {private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}private Singleton() {}public static final Singleton getInstance() {return SingletonHolder.INSTANCE;}
}

6、枚举

JDK 版本:JDK1.5 起

是否 Lazy 初始化:否

是否多线程安全:是

**实现难度:**易

**描述:**这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
不能通过 reflection attack 来调用私有构造方法。

实例

public enum Singleton {INSTANCE;public void whateverMethod() {}
}

**经验之谈:**一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。

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

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

相关文章

创造性模式——原型模式

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建…

Java集合:关于 LinkedList 的内容盘点

本篇内容包括:LinkedList 的概述、LinkedList 的结构既双向链表实现与LinkedList-Node 结构、LinkedList 的使用(构造方法&常用方法)、关于 Queue 队列的介绍、关于 ArrayList 和 LinkedList 的区别以及算法:翻转链表&#xf…

oracle迁移父子数据

现有需求如下,业务组织单元表中id字段数据在另外一个系统全部重复,但需要将此业务单元组织导入另一系统 业务组织单元表Isc_Specialorg_Unit 表中存在ID字段为子节点数据,parent_id为父节点数据,orgpath为组织路径 现在做如下操…

oracle表空间

概念 表空间和数据文件 ●表空间是逻辑存储概念,一个表空间是一个或多个数据文件的逻辑集合 ●存储对象(表、索引)逻辑的存储在表空间上,而存储对象的数据物理的存放在数据文件上 ●数据库至少需要一个叫做system的表空间,也就是系统表空间 ●…

oracle日志

日志分类 redo log files联机日志或重做日志 archived log files归档日志 1184198alert log files 告警日志 trace files user_ _dump_ _dest 用户信息日志如跟踪会话日志 background dump_ dest进程日志还有其他一-些不常用的日志 v$database的log_mode 数据库归档模式…

MySQL:分库分表知识点盘点

本篇内容包括:数据库瓶颈、分库分表以及分库分表相关问题 一、数据库瓶颈 不管是IO瓶颈,还是CPU瓶颈,最终都会导致数据库的活跃连接数增加,进而逼近甚至达到数据库可承载活跃连接数的阈值。在业务Service来看就是,可用…

oracle的sga

sga SGA的管理 ■有三种方式: ●8i:SGA的总大小由所有内存组件大小之和决定,不能直接定义SCA大小。对内部组件大小的修改必须在数据库重起 后 才能生为,所以叫做SGA的静态管理。 ●9i&#xff…

MySQL:主从相关知识点盘点

本篇内容包括:MySQL 主从复制简介、主从复制的原理以及主从搭建 一、MySQL 主从复制简介 在实际的生产中,为了解决Mysql的单点故障已经提高MySQL的整体服务性能,一般都会采用**「主从复制」**。 比如:在复杂的业务系统中&#x…

oracle导出expdp

expdp/impdp 是 oracle 10g 版本后推出的服务器端工具--数据泵,作用是为了将数据库中的逻辑对象,比如表、用户、全库、表空间进 行导入,导出 命令解析 ATTACH ATTACH [[schema_name.]job_name] schema_name 表示用户名,job_name 表示导出的作…

oracle检查点

检 查 点 概述 ■l当修改数据时,需要首先将数据读入内存中(Buffer Cache),修改数据的同时,Oracle会记录重做信息(Redo)用于恢复。因 为有了重做信息的存在,Oracle不需要在提交时…

文献翻译:Java theory and practice: Fixing the Java Memory Model, Part 2

Java theory and practice: Fixing the Java Memory Model, Part 2 译:Java 理论与实践:修复 Java 内存模型,第 2 部分 翻译自:http://www.ibm.com/developerworks/library/j-jtp03304/(原文写于2004年3月&#xff0…

常用系统函数oracle

单行函数日期函数ADD_MONTHS(date, n):用于从一个日期值增加或减少一些月份 MONTHS_BETWEEN(date1, date2):判断两个日期之间相差的月份 LAST_DAY(date): 函数返回包含日期的月份的最后一天 ROUND(date[,fmt]): 将日期d按照fmt指定的格式舍入,fmt为字符串 TRU…

oracle游标

游标(cursor )是一个私有的SQL工作区域,是一个指向上下文区的句柄或指针,位于内存中的 "临时表"。 游标是SQL的一个内存工作区,由系统或用户以变量的形式定义。游标的作用就是用于临时存储从数据库中提取的数据块。在某…

系统学习SpringFramework:Spring IOC

本篇内容包括:IOC 和 DI 的概念、Spring 容器,即 BenaFactory 与 AplicationConext 等 IOC 相关内容。 一、IOC 和 DI 的概念 1、IOC IoC(Inversion of control )即“控制反转”,它是一种设计思想而非一个技术实现。…

rowid

ROWID的格式rowidOOOOOOFFFBBBBBBRRR说明数据对象号相对文件号数据块号行号 上述表格是Oracle 9i及以上版本数据库中的rowid格式: 6位对象号3位相对文件号6位数据块号3位行号,是一个18位的64进制值。这个18位的64进制值在数据库内却是以10个bytes合计80个bit的二进制…

系统学习SpringFramework:循环依赖与三级缓存

本篇内容包括:Spring 中的循环依赖问题(包括 Spring 中的循环依赖问题和Spring 中的循环依赖的 5 种场景的介绍)、Spring 三级缓存介绍、4 个 Spring 无法自动解决的循环以来场景以及其对应的手动解决方式。 一、Spring 中的循环依赖问题 1、…

深入理解Java虚拟机:Java类的加载机制

本篇内容包括:Java 类的加载机制(Jvm 结构组成、Java 类的加载)、类的生命周期(加载-验证-准备-解析-初始化-使用-卸载)、类加载器 以及 双亲委派模型。 一、Java 类的加载机制 1、 Jvm 结构组成 Jvm 整体组成可分为…

新版谷歌浏览器开启Flash支持

浏览器地址栏中输入chrome://version查看Chrome浏览器、Flash插件的版本信息。 Chrome 69.0-70.0版本Chrome 71.0-74.0及以后版本谷歌浏览器地址栏中输入【chrome://flags/#enable-ephemeral-flash-permission】,将【Enable Ephemeral Flash Permissions】从【Defau…

深入理解Java虚拟机:Java垃圾回收机制

本篇内容包括:JAVA 垃圾回收机制概述、有哪些内存需要回收、如何回收(标记-清除、标记-整理(标记-清除-压缩)、复制(标记-复制-清除)、分代收集等算法) 以及 何时进行垃圾回收等内容&#xff01…

深入理解Java虚拟机:Java垃圾回收器

本篇内容包括:7 种 Jvm 垃圾回收器的介绍、对比 以及 对应的 Jvm 参数设置,这 7 种包括了:Serial、ParNew 以及 Parallel Scavenge 三种新生代回收器 和 :Serial Old、Parallel Old 以及 CMS 三种老年代回收器,此外还有…