热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

设计模式之单例模式(二)

设计模式之单例模式(二),Go语言社区,Golang程序员人脉社

上一篇我们对经典的单例模式进行了学习,并且知道了单例模式的概念,以及如何通过单线程去创建一个有效的单例模式,让程序不用多次去创建实例。

但是,通过巧克力工厂的实践,我们很想知道在多线程模式下,这个到底会是什么情况呢?所以,就有了我们继续学习的目标啦。原来单例模式,不简单呀。

多线程的麻烦

首先,我们还是看下巧克力工厂经典单例的代码:原本在单线程模式下,运行的还是挺好的,工厂里那些小心翼翼的代码都可以去掉了;但是忽然用了多线程,发现还是创建了多个实例,当工厂很郁闷。

public static ChocolateBoiler getInstance() {
    if (uniqueInstance == null) {
        uniqueInstance = new ChocolateBoiler(;)
    }
    return uniqueInstance;
}

仔细查看代码,你知道为什么了吗?这里有两个线程都要执行这段代码,那么Java的JVM在进入代码的时候肯定会有先后顺序,有没有可能是JVM搅乱了代码,让getInstance()方法内部出了问题呢?对的,就是这样。请看下图:

当在多线程环境下,线程1和线程2都进入了getInstance方法,那么,此时通过判断null的方式来,势必在JVM内部,发生了交叉的事情,然后,然后你的工厂就创建了两个实例,挺悲剧的。

处理多线程

熟悉Java的朋友都知道,最轻易地解决这个多线程的方法就是把getInstantce()变成synchronized方法,比如:

public static synchronized Singleton getInstance() {
    if(uniqueInstance == null) {
        uniqueInstance = new Singleton();
    }
    return uniqueInstance;
}

通过增加synchronized关键字到getInstance()方法中,每个线程在进入这个方法之前,需要先等候别的线程离开该方法,也就是说,不会有两个线程可以同时进入这个方法。

但是,问题来了:我们其实只有第一次执行此方法时,才真正需要同步。换句话说,一旦设置好uniqueInstance变量,就不需要同步这个方法了。之后每次调用这个方法,如果还是同步进行的话,给资源造成了很大的浪费,也是一种累赘。

能改善多线程吗?

为了符合大多数Java应用程序、我们还是需要确保单例模式能在多线程的情况下正常工作的。但是同步的getInstance()的做法将拖垮性能,该怎么办呢?

  1. 如果getInstance()的性能对应用程序不是很关键,就什么都别做

没错,如果你的应用程序可以接受getInstance()造成的额外负担,就忽略了吧。同步getInstance()的方法既简单又有效。但是你必须知道,同步一个方法可能造成程序执行效率下降100倍。所以,还是得好好考虑应用场景哦。

  1. 使用“急切”创建实例,而不用延迟实例化的做法

如果应用程序总是创建并使用单例实例,或者在创建和运行时方面的负担不太繁重,你可能想要急切(eagerly)创建此单例,比如:

public class Singleton {
    // 在静态初始化中创建单例,这段代码保证了线程安全
    private static Singleton uniqueInstance = new Singleton();
    
    private Singltton() {}
    
    public static Singleton getInstance() {
        return uniqueInstance;
    }
}

利用这个做法,我们依赖JVM在加载这个类时马上创建此唯一的单例实例。JVM保证在任何线程访问uniqueInstance静态变量之前,一定先创建。哈哈,有没有,这就是饿汉式。

  1. 用双重检查加锁,在getInstance()中减少使用同步

利用双重检查加锁(double-checked locking),首先检查是否实例已经创建了,如果尚未创建,才进行同步。这样 依赖,只有第一次会同步,这正是我们想要的。

public class Singleton {
    private volatile static Singleton uniqueInstance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
    // 检查实例,如果不存在,就进入同步区块,只有第一次才彻底执行这里的代码
        if (uniqueInstance == null) {
            synchronized (Singleton.class) {
    // 进去区块后,再检查一次,如果仍是null,才创建实例
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

volatile关键词确保:当uniqueInstance变量被初始化成Singleton实例时,多个线程正确地处理uniqueInstance变量。

因为单例模式大家用的比较多,而且思路也比较清晰了,那我就在这里把这章总结也一并附上了。

设计箱内的工具

还是按照之前的套路,总结下工具箱内新增的工具吧

  • OO基础

    抽象、封装、继承、多态

  • OO原则

    封装变化

    多用组合,少用继承

    针对接口编程,不针对实现编程

    为交互对象之间的松耦合设计而努力

    依赖抽象,不要依赖具体类

    类应该对扩展开放,对修改关闭

  • OO模式

    『策略模式』、『观察者模式』、『装饰者模式』、『抽象工厂模式』、『工厂方法模式』

    单例模式』确保一个类只有一个实例,并提供全局访问点

好的,很开心有没有,又学会了一个设计模式,还是我们经常使用的设计模式之一。下一次,我们将学习命令模式,和大家不见不散。

爱生活,爱学习,爱感悟,爱挨踢


推荐阅读
  • 本文介绍了多个关于JavaScript的书籍资源、实用工具和编程实例,涵盖从入门到进阶的各个阶段,帮助读者全面提升JavaScript编程能力。 ... [详细]
  • JavaScript 基础语法指南
    本文详细介绍了 JavaScript 的基础语法,包括变量、数据类型、运算符、语句和函数等内容,旨在为初学者提供全面的入门指导。 ... [详细]
  • 本文探讨了如何在日常工作中通过优化效率和深入研究核心技术,将技术和知识转化为实际收益。文章结合个人经验,分享了提高工作效率、掌握高价值技能以及选择合适工作环境的方法,帮助读者更好地实现技术变现。 ... [详细]
  • 本文详细介绍了C语言中的指针,包括其基本概念、应用场景以及使用时的优缺点。同时,通过实例解析了指针在内存管理、数组操作、函数调用等方面的具体应用,并探讨了指针的安全性问题。 ... [详细]
  • 云计算的优势与应用场景
    本文详细探讨了云计算为企业和个人带来的多种优势,包括成本节约、安全性提升、灵活性增强等。同时介绍了云计算的五大核心特点,并结合实际案例进行分析。 ... [详细]
  • 本文探讨了Java编程的核心要素,特别是其面向对象的特性,并详细介绍了Java虚拟机、类装载器体系结构、Java类文件和Java API等关键技术。这些技术使得Java成为一种功能强大且易于使用的编程语言。 ... [详细]
  • Python处理Word文档的高效技巧
    本文详细介绍了如何使用Python处理Word文档,涵盖从基础操作到高级功能的各种技巧。我们将探讨如何生成文档、定义样式、提取表格数据以及处理超链接和图片等内容。 ... [详细]
  • 本文详细介绍了优化DB2数据库性能的多种方法,涵盖统计信息更新、缓冲池调整、日志缓冲区配置、应用程序堆大小设置、排序堆参数调整、代理程序管理、锁机制优化、活动应用程序限制、页清除程序配置、I/O服务器数量设定以及编入组提交数调整等方面。通过这些技术手段,可以显著提升数据库的运行效率和响应速度。 ... [详细]
  • 在Java中,this是一个引用当前对象的关键字。如何通过this获取并显示其所指向的对象的属性和方法?本文详细解释了this的用法及其背后的原理。 ... [详细]
  • 开发笔记:9.八大排序
    开发笔记:9.八大排序 ... [详细]
  • PHP 过滤器详解
    本文深入探讨了 PHP 中的过滤器机制,包括常见的 $_SERVER 变量、filter_has_var() 函数、filter_id() 函数、filter_input() 函数及其数组形式、filter_list() 函数以及 filter_var() 和其数组形式。同时,详细介绍了各种过滤器的用途和用法。 ... [详细]
  • Kubernetes 持久化存储与数据卷详解
    本文深入探讨 Kubernetes 中持久化存储的使用场景、PV/PVC/StorageClass 的基本操作及其实现原理,旨在帮助读者理解如何高效管理容器化应用的数据持久化需求。 ... [详细]
  • 深入理解Lucene搜索机制
    本文旨在帮助读者全面掌握Lucene搜索的编写步骤、核心API及其应用。通过详细解析Lucene的基本查询和查询解析器的使用方法,结合架构图和代码示例,带领读者深入了解Lucene搜索的工作流程。 ... [详细]
  • 深入解析for与foreach遍历集合时的性能差异
    本文将详细探讨for循环和foreach(迭代器)在遍历集合时的性能差异,并通过实际代码示例和源码分析,帮助读者理解这两种遍历方式的不同之处。文章内容丰富且专业,旨在为编程爱好者提供有价值的参考。 ... [详细]
  • 采用IKE方式建立IPsec安全隧道
    一、【组网和实验环境】按如上的接口ip先作配置,再作ipsec的相关配置,配置文本见文章最后本文实验采用的交换机是H3C模拟器,下载地址如 ... [详细]
author-avatar
干将stambach_611
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有