从零开始学设计模式(三)——单例模式(Singleton Pattern)
单例模式(Singleton Pattern)
单例模式也属于创建型模式,难度等级为初级,是Java中最简单和最常见的设计模式之一。由于其常见性,单例模式的实现方法衍生出很多种,不同的实现方式在延迟加载、线程安全、性能上各有千秋,后面我们会在程序代码说明章节中来具体分析。
单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一对象的方式,可以直接访问,不需要实例化该类的对象。
意图
确保一个类只有一个实例,并提供对它的全局访问点
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当你想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
解释
现实世界的例子
只有一座象牙塔可以让巫师们研究他们的魔法。巫师们总是使用同样的魔法象牙塔。这里的象牙塔是单例
简而言之
确保一个特定类永远只创建一个对象实例
维基百科
In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system.(在软件工程中,singleton模式是一种软件设计模式,它将类的实例化限制为一个对象。当只需要一个对象来协调整个系统的动作时,这很有用)
程序代码说明
1、懒汉式,线程不安全的
public class Singleton{ private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
这种方式是最基本的实现方式,但由于其不支持多线程,getInstance方法没有加锁,并发的情况下可能产生多个instance实例,所以严格意义上来说它并不算单例模式,但它做到了Lazy初始化。
这种方式目前已不建议使用
2、懒汉式,线程安全的
以上面巫师们的象牙塔为例子
/** * Thread-safe Singleton class. The instance is lazily initialized and thus needs synchronization * mechanism. * * Note: if created by reflection then a singleton will not be created but multiple options in the * same classloader */ public final class ThreadSafeLazyLoadedIvoryTower { private static ThreadSafeLazyLoadedIvoryTower instance; private ThreadSafeLazyLoadedIvoryTower() { // protect against instantiation via reflection if (instance == null) { instance = this; } else { throw new IllegalStateException("Already initialized."); } } /** * The instance gets created only when it is called for first time. Lazy-loading */ public static synchronized ThreadSafeLazyLoadedIvoryTower getInstance() { if (instance == null) { instance = new ThreadSafeLazyLoadedIvoryTower(); } return instance; } }
这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是由于加了同步锁,效率很低,99%情况下不需要同步。对于getInstance()的性能要求不是很高的应用程序可以考虑使用(该方法使用不频繁)
3、双检锁/双重校验锁(DCL,即 double-checked locking)
此方式需在JDK1.4之后,采用双锁机制,安全且在多线程情况下保持了较高的性能。
public final class ThreadSafeDoubleCheckLocking { private static volatile ThreadSafeDoubleCheckLocking instance; /** * private constructor to prevent client from instantiating. */ private ThreadSafeDoubleCheckLocking() { // to prevent instantiating by Reflection call if (instance != null) { throw new IllegalStateException("Already initialized."); } } /** * Public accessor. * * @return an instance of the class. */ public static ThreadSafeDoubleCheckLocking getInstance() { // local variable increases performance by 25 percent // Joshua Bloch "Effective Java, Second Edition", p. 283-284 ThreadSafeDoubleCheckLocking result = instance; // Check if singleton instance is initialized. If it is initialized then we can return the instance. if (result == null) { // It is not initialized but we cannot be sure because some other thread might have initialized it // in the meanwhile. So to make sure we need to lock on an object to get mutual exclusion. synchronized (ThreadSafeDoubleCheckLocking.class) { // Again assign the instance to local variable to check if it was initialized by some other thread // while current thread was blocked to enter the locked zone. If it was initialized then we can // return the previously created instance just like the previous null check. result = instance; if (result == null) { // The instance is still not initialized so we can safely (no other thread can enter this zone) // create an instance and make it our singleton instance. instance = result = new ThreadSafeDoubleCheckLocking(); } } } return result; } }
4、饿汉式
这种方式比较常用,但容易产生垃圾对象,没有锁机制,执行效率很高,但未实现Lazy初始化。
** * Singleton class. Eagerly initialized static instance guarantees thread safety. */ public final class IvoryTower { /** * Private constructor so nobody can instantiate the class. */ private IvoryTower() {} /** * Static to class instance of the class. */ private static final IvoryTower INSTANCE = new IvoryTower(); /** * To be called by user to obtain instance of the class. * * @return instance of the singleton. */ public static IvoryTower getInstance() { return INSTANCE; } }
基于classloader机制避免了多线程的同步问题,不过由于IvoryTower类在装载时就已经实例化了,显然没有达到lazy loading的效果。
5、登记式/静态内部类
这种方式可以达到上面第三种双检锁一样的功效,具备了延迟加载和线程安全,且实现简单。
对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
public final class InitializingOnDemandHolderIdiom { /** * Private constructor. */ private InitializingOnDemandHolderIdiom() {} /** * @return Singleton instance */ public static InitializingOnDemandHolderIdiom getInstance() { return HelperHolder.INSTANCE; } /** * Provides the lazy-loaded Singleton instance. */ private static class HelperHolder { private static final InitializingOnDemandHolderIdiom INSTANCE = new InitializingOnDemandHolderIdiom(); } }
这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 4 种方式不同的是:第 4种方式只要 IvoryTower 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 InitializingOnDemandHolderIdiom 类被装载了,instance 不一定被初始化。因为 HelperHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 HelperHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以我们想让它延迟加载,这种方式相比第 4 种方式就显得更合理。
此方式对JDK版本也无任何要求,在现在能看到的JDK版本下都支持。
6、枚举
Joshua Bloch, Effective Java 2nd Edition p.18 中提出了使用枚举类来实现单例模式,书中指出这是最好的一种方式来实现单例。
A single-element enum type is the best way to implement a singleton
/** * Enum based singleton implementation. Effective Java 2nd Edition (Joshua Bloch) p. 18 * * This implementation is thread safe, however adding any other method and its thread safety * is developers responsibility. */ public enum EnumIvoryTower { INSTANCE; public void whateverMethod() { // .... do something } @Override public String toString() { return getDeclaringClass().getCanonicalName() + "@" + hashCode(); } }
然后我们使用它
EnumIvoryTower enumIvoryTower1 = EnumIvoryTower.INSTANCE; EnumIvoryTower enumIvoryTower2 = EnumIvoryTower.INSTANCE; assertEquals(enumIvoryTower1, enumIvoryTower2); // true
总结一下:一般情况下,方式1和2不建议使用,在明确不需要实现lazy loading的时候可以优先选择方式4.在需要lazy loading的时候,可以选择方式3和5。如果还涉及到反序列化创建对象时可以使用最佳方式6
应用场景
当遇到如下情况时可考虑使用单例模式:
- 一个类必须只有一个实例,客户端必须可以从众所周知的通道访问它
- 一个单实例应该可以被子类可扩展,并且客户端能够使用扩展实例而无需修改其代码
使用场景
- 日志记录类
- 管理一个数据库连接
- 文件管理器
Java中的现实例子
优缺点
优点:
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
2、避免对资源的多重占用(比如写文件操作)。
缺点:
1、没有接口,不能继承,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
2、通过控制自己的创建和生命周期,违反了单一责任原则SRP(Single Responsibility Principle)
3、创建紧密耦合的代码,单例模式的客户端变得难以测试
写在最后
单例模式的使用在Java应用中是非常普遍的,Spring 管理bean 实例默认配置就是单例模式。
下一章节我将介绍建造者模式(Builder Pattern)
码字不易,各位看官如果喜欢的话,请给点个赞 ️,关注下我,我将努力持续不断的更新
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Sublimne text3配置python3和robot开发环境
安装Sublime Package Control 1、打开Preferences菜单,并选择 Browse Packages… 2、系统会打开Sublime Text 2的Packages文件夹,回到上一级菜单,然后打开Installed Packages文件夹; 3、下载并将下载的Package Control.sublime-package到 Installed Packages文件夹(注意此处是Installed Packages,不是Packages文件夹); 4、重启Sublime Text 3; 使用Ctrl+Shift+P打开命令行模式,在里面输入Install Package(或IP)即可搜索需要的Package。一般使用“ConvertToUTF8”和“GBK upport”即可正常读取中文字符; 配置Python3编译环境+Robot插件 点击Tools->Build System->new Build System 修改配置文件: { "cmd": ["C:/python37/python.exe", "-u", "$file"], "file_re...
- 下一篇
WPF C# 多屏情况下,实现窗体显示到指定的屏幕内
原文: WPF C# 多屏情况下,实现窗体显示到指定的屏幕内 针对于一个程序,需要在两个显示屏上显示不同的窗体,(亦或N个显示屏N个窗体),可以使用如下的方式实现。 主要涉及到的:System.Windows.Forms.Screen.AllScreens和WindowState属性 1、首先Window.WindowState属性,其类型是一个枚举,若想在指定的屏幕上显示WindowState属性不能为Maximized,当为Maximized时窗体会显示在主屏上。 namespaceSystem.Windows { //指定是最小化、最大化还是还原窗口 public enum WindowState { //还原窗口。 Normal = 0, //最小化窗口。 Minimized = 1, //最大化窗口。 Maximized = 2 } } 2、System.Windows.Forms.Screen.AllScreens属性提供获取获取系统上所有显示器的数组,因此我们通过此属性获取各个屏幕的参数,从而使用Window.Top和Window.Left设置偏移参数实现窗体显示...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS7设置SWAP分区,小内存服务器的救世主
- Docker快速安装Oracle11G,搭建oracle11g学习环境