数据结构与算法--实现Singleton模式

题目:设计一个类,我们只生成该类的一个实例。

  • 只生成一个实例的类就是实现Singleton(单例)模式的类型。本题其实主要考察我们设计模式,因为面试的时候先来一个简单的,并且喜欢面设计模式相关的题目,而且,在常用的设计模式中,Singleton是比较简单的,而且可以通过简洁的代码来实现。所有Singleton是常见的面试题目。

以下解题思路

  • 由于只生成一个实例,我们可以将构造方法设置成私有构造,使得其他方法无法通过实例创建。我们可以定义一个静态实例,在需要的时候创建该实例,如下思路:
解法一:只适用单线程
//不好解法一
/*** @author liaojiamin* @Date:Created in 10:18 2020/10/27*/
public class Singleton {private Singleton(){}public static Singleton singleton = null;public static Singleton getInstance(){if(null == singleton){singleton = new Singleton();}return singleton;}
}
  • 分析,以上代码是在不考虑并发的情况下的简单单例模式,从下面几点来保证了得到的实例是唯一的:
    • 静态实例:带有static关键字的熟悉在每个类中都是唯一的(在class文件被加载到jvm中的准备阶段,方法区为这些类变量进行内存分配,并且进行初始化。比如被static修饰的字段。非static修饰属性会在类实例化时候在对内存中分配存储空间。因此类变了在class文件被加载的时候才有,并不受实例化的影响)
    • 私有构造方法限制客户通过实例创建
    • 提供getInstance唯一入口
  • 以上代码存在并发问题,用如下方法进行检测:
/*** @author liaojiamin* @Date:Created in 14:44 2020/10/27*/
public class TestSingletonRunnableMain {private Boolean lock;public Boolean getLock() {return lock;}public void setLock(Boolean lock) {this.lock = lock;}public static void main(String[] args) throws InterruptedException {Long startTime = System.currentTimeMillis();int num = 100;final CyclicBarrier cyclicBarrier = new CyclicBarrier(num);final Set<String> set = Collections.synchronizedSet(new HashSet<String>());ThreadFactory nameThreadFactory = new ThreadFactoryBuilder().setNameFormat("nameThreadFactory-01").build();ExecutorService executorService = new ThreadPoolExecutor(100, 100, 1,TimeUnit.SECONDS, new LinkedBlockingQueue<>(100), nameThreadFactory, new ThreadPoolExecutor.CallerRunsPolicy());for (int i = 0; i < num; i++) {executorService.execute(new Runnable() {@Overridepublic void run() {try {cyclicBarrier.await();Singleton  singletonThree = Singleton.getInstance();set.add(singletonThree.toString());} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}}});}Thread.sleep(2000);System.out.println("in more thread get singleton");for (String s : set) {System.out.println(s);}executorService.shutdown();}
}
//输出:
//in more thread get singleton
//com.ljm.resource.math.Singleton@53fe75ec
//com.ljm.resource.math.Singleton@4eba943c
解法二:并发安全,但是效率低
/*** @author liaojiamin* @Date:Created in 14:33 2020/10/27*/
public class SingletonOne {private SingletonOne(){}public static SingletonOne singletonOne;public static SingletonOne getInstance(){synchronized (SingletonOne.class){if(null == singletonOne){singletonOne = new SingletonOne();}return singletonOne;}}
}
  • 区别在于获取对象时,用synchronized关键字进行加锁,同一时刻只能有一个线程执行,等第一个线程创建完时间后。第一个线程释放同步锁,此时第二个线程可以加上同步锁,并允许接下来的代码。这个时候,实例已经被第一个线程创建,所以第二个线程不会再重复创建实例,保证得到的是同一个实例(synchronized加锁是一个非常耗时的操作,应该尽量避免)
可行的解法:加同步锁前后两次判断
  • 我们可以优化以上方法,只在我们需要的时候镜像同步锁,如下。
/*** @author liaojiamin* @Date:Created in 14:38 2020/10/27*/
public class SingletonTwo {private SingletonTwo(){}public static SingletonTwo singletonTwo;public static SingletonTwo getInstance(){if(null == singletonTwo){synchronized (SingletonOne.class){if(null == singletonTwo){singletonTwo = new SingletonTwo();}}}return singletonTwo;}
}
  • 以上SingletonTwo中只有instance为null的时候,才需要加锁。当instance已经创建出来后,无须加锁。因为只有第一次instance为null,所以执行的结果就是只有第一次的时候才会加锁,其他时候都无需锁,所有效率高得多。
  • 两次判断的原因:
    • 假设我们去掉同步块中的是否为null的判断,有这样一种情况,假设A线程和B线程都在同步块外面判断了synchronizedSingleton为null,结果A线程首先获得了线程锁,进入了同步块,然后A线程会创造一个实例,此时synchronizedSingleton已经被赋予了实例,A线程退出同步块,直接返回了第一个创造的实例,此时B线程获得线程锁,也进入同步块,此时A线程其实已经创造好了实例,B线程正常情况应该直接返回的,但是因为同步块里没有判断是否为null,直接就是一条创建实例的语句,所以B线程也会创造一个实例返回,此时就造成创造了多个实例的情况。
推荐的解法:利用静态属性
/*** @author liaojiamin* @Date:Created in 14:40 2020/10/27*/
public class SingletonThree {private SingletonThree(){}private static SingletonThree singletonThree = new SingletonThree();public static SingletonThree getInstance(){return singletonThree;}
}
  • SingletonThree实现的方式简洁。我们初始化静态变量singletonThree 时候创建一个实例。由于Java是在类加载的时候在方法区对静态属性分配内存,并且只初始化一次,这样我们就能保证只初始化一次singletonThree 。
  • 因为SingletonThree 中singletonThree 的初始化并不是调用getInstance的时候创建的,而且在类加载的时候就已经创建,我们使用的时候调用getInstance方法他其实是不会创建新的实例,所有他会提前创建好实例,不管你之后是否需要
最优的解法:实现按需创建实例
/*** @author liaojiamin* @Date:Created in 15:45 2020/10/27*/
public class SingletonFour {private SingletonFour(){}private static class SingletonInstance{static SingletonFour singletonFour = new SingletonFour();}public static SingletonFour getInstance(){return SingletonInstance.singletonFour;}
}
  • SingletonFour 中我们定义了一个私有的内部类SingletonInstance我们利用:**内部静态类不会自动初始化,只有调用静态内部类的方法,静态域,或者构造方法的时候才会加载静态内部类。**的特点来实现的此处的单例
  • 由于静态内部类是私有的,只有我们在调用getInstance方法的时候被用到,因此当我们试图通过属性SingletonFour .getInstance得到SingletonFour 时候,会自动调用内部类SingletonInstance的静态构造方法创建实例,并初始化内部类中的静态变量 singletonFour

解法比较

  • 以上五种实现方案中,第一张方法在多线程环境中不能正常工作,第二种线程安全的方法但是实际效率低下,都不是我们所期待的可运行的解法。第三种方法中,我们通过两次判断加一次锁确保在多线程环境能高效运转。第四种利用java静态属性的特性,确保值创建一个实例。第五种方法利用私有嵌套类型的特性,做到只在真正需要的时候才创建实例,提高空间使用率。第五种解法是最优解。

下一篇: 数据结构与算法–数组:二维数组中查找

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

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

相关文章

[剑指offer]面试题37:两个链表的第一个公共结点

面试题37&#xff1a;两个链表的第一个公共结点 题目&#xff1a;输入两个链表&#xff0c;找出它们的第一个公共结点。链表结点定义如下&#xff1a; struct ListNode {int val;ListNode *next;ListNode(int x) : val(x), next(NULL) {} };思路: 两个链表长度分别为L1C、L2C&…

了解.NET中的垃圾回收

原文来自互联网&#xff0c;由长沙DotNET技术社区编译。尽管这是一篇来自2009年的古老的文章&#xff0c;但或许能够对你理解GC产生一些作用。 了解.NET中的垃圾回收一旦了解了.NET的垃圾收集器是如何工作的&#xff0c;那么可能会触及.NET应用程序的一些更为神秘的问题时&…

数据结构与算法--数组:二维数组中查找

数组 数组最简单的是数据结构&#xff0c;占据一整块连续的内存并按照顺序存储数据&#xff0c;创建数组时候&#xff0c;我们需要首先指定数组的容量大小&#xff0c;然后根据大小分配内存。即使我们只在数组中存储一个元素&#xff0c;亚需要为所有数据预先分配内存&#xf…

[剑指offer]面试题41:和为s的两个数字VS和为s的连续正数序列

面试题41&#xff1a;和为s的两个数字VS和为s的连续正数序列 题目一&#xff1a;输入一个递增排序的数组和一个数字s&#xff0c;在数组中查找两个数&#xff0c;使得它们的和正好是s。如果有多对数字的和等于s&#xff0c;输出任意一对即可。 代码如下: bool FindNumbersWit…

数字化演化历史

回顾历史&#xff0c;帮助我们展望未来&#xff0c;在数字化技术出现之前&#xff0c;人类历史几千年的历史中&#xff0c;人类社会中&#xff0c;人类的大脑是唯一可以作信息处理的。比如我们发明了汽车&#xff0c;需要人来开车&#xff1b;发明了飞机&#xff0c;需要人来驾…

数据结构与算法--字符串:字符串替换

数据结构与算法–字符串&#xff1a;字符串替换 字符串的优化 由于字符串在编程时候使用的评率非常高&#xff0c;为了优化&#xff0c;很多语言都对字符串做了特殊的规定。下面我们讨论java中字符串的特性java中的字符数组以’\0’ 结尾&#xff0c;我们可以利用这个特性来找…

[剑指offer]面试题42:翻转单词顺序 VS左旋转字符串

面试题42&#xff1a;翻转单词顺序 VS左旋转字符串 题目一&#xff1a;输入一个英文句子&#xff0c;翻转句子中单词的顺序&#xff0c;但单词内字符的顺序不变。为简单起见&#xff0c;标点符号和普通字母一样处理。例如输入字符串"I am a student."&#xff0c;则输…

数据结构与算法--经典10大排序算法(动图演示)【建议收藏】

十大经典排序算法总结&#xff08;动图演示&#xff09; 算法分类 十大常见排序算法可分为两大类&#xff1a; 比较排序算法&#xff1a;通过比较来决定元素的位置&#xff0c;由于时间复杂度不能突破O(nlogn)&#xff0c;因此也称为非线性时间比较类排序非比较类型排序&…

如何查找,修复和避免C#.NET中内存泄漏的8个最佳实践

原文来自互联网&#xff0c;由长沙DotNET技术社区编译。本文来源&#xff1a;https://michaelscodingspot.com/find-fix-and-avoid-memory-leaks-in-c-net-8-best-practices/从事大型企业项目的任何人都知道内存泄漏就像是大型酒店中的老鼠。当它们很少时&#xff0c;您可能不会…

[剑指offer]面试题47:不用加减乘除做加法

面试题47&#xff1a;不用加减乘除做加法 题目&#xff1a;写一个函数&#xff0c;求两个整数之和&#xff0c;要求在函数体内不得使用、-、、四则运算符号。 代码如下: int add(int num1, int num2) {int sum, carry;do{sum (num1 ^ num2);carry (num1 & num2) <&l…

ASP.NET Core技术研究-探秘依赖注入框架

ASP.NET Core在底层内置了一个依赖注入框架&#xff0c;通过依赖注入的方式注册服务、提供服务。依赖注入不仅服务于ASP.NET Core自身&#xff0c;同时也是应用程序的服务提供者。毫不夸张的说&#xff0c;ASP.NET Core通过依赖注入实现了各种服务对象的注册和创建&#xff0c;…

Redis遍历方式思考--字典扩容方式

全量遍历keys 工作中线上Redis维护&#xff0c;有时候我们需要查询特定前缀的缓存key列表来手动处理数据。可能是修改值&#xff0c;删除key。那么怎么才能快速的从海量的key中查找到对应的前缀匹配项。Redis提供了一下简单的指令&#xff0c;例如keys用来满足特定正则下的key…

[剑指offer]面试题48:不能被继承的类

面试题48&#xff1a;不能被继承的类 题目&#xff1a;用C设计一个不能被继承的类。 ❖ 常规的解法&#xff1a;把构造函数设为私有函数 很多人都能够想到&#xff0c;在 C中子类的构造函数会自动调用父类的构造函数&#xff0c;子类的析构函数也会自动调用父类的析构函数。 …

从项目到产品: 软件时代需要价值流架构师 | IDCF

译者&#xff1a;无敌哥原文地址: https://thenewstack.io/the-age-of-software-needs-value-stream-architects/ 本文翻译仅供学习交流之用。原文作者 Mik Kersten 出版了《Project to Product》本系列共四篇文章&#xff0c;分别是01 从项目到产品&#xff1a;软件需要从物理…

Redis高效性探索--线程IO模型,通信协议

Redis线程IO模型 Redis是单线程&#xff0c;这个毋庸置疑Redis单线程能做到这么高的效率&#xff1f;不用怀疑&#xff0c;还有很多其他的服务都是单线程但是也有超高的效率&#xff0c;比如Node.js&#xff0c;Nginx也是单线程。Redis单线程高效原因&#xff1a; Redis所有数…

[剑指offer]面试题45:圆圈中最后剩下的数字

面试题45&#xff1a;圆圈中最后剩下的数字 题目&#xff1a;0,1,…,n-1这n个数字排成一个圆圈&#xff0c;从数字0开始每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。 ❖ 经典的解法&#xff0c;用环形链表模拟圆圈 代码如下: #include <iostream&…

Redis持久化-深入理解AOF,RDB

持久化 Redis数据全部在内存中&#xff0c;如果宕机&#xff0c;数据必然丢失&#xff0c;因此必须有一种机制保证Redis数据不会因为故障丢失&#xff0c;这就是Redis的持久化机制持久化方式两种&#xff1a;AOF&#xff0c;RDB&#xff0c;如下图 RDB快照模式是一次全量备份&…

推荐一个集录屏、截图、音频于一体的软件给大家

捕获屏幕&#xff0c;网络摄像头&#xff0c;音频&#xff0c;光标&#xff0c;鼠标单击和击键GitHub&#xff1a;https://github.com/MathewSachin/Captura特性 免费 100%免费&#xff0c;你不需要花一分钱开源 根据MIT许可的条款&#xff0c;可以在Github上获得Captura的源…

C++实现各种插入排序(直接,折半,希尔)

直接插入排序(无哨兵): 代码如下: #include <iostream> using namespace std;//数组下标从0开始 void InsertSort(int *a, int len) {int j;for (int i 1; i < len; i){if (a[i - 1] > a[i])//优化一下{int temp a[i];for (j i - 1; a[j] > temp &&…

Redis高效性探索--管道

管道 开始接触Redis时候&#xff0c;对应Redis管道有一个错误认识&#xff0c;任务是redis服务器提供的一种特别的技术&#xff0c;有了这种技术可以加速Redis的存取效率&#xff0c;但是实际上Redis的管道计算&#xff08;Pipeline&#xff09;本身是客户端提供的技术&#x…