剑指offer算法 java实现 单例模式

线程安全问题以及单例模式 在这段代码中,Sc.next()是带有阻塞的操作等待用户在控制台输入,notify这里同样也是需要先拿到锁再进行notify(属于java给出的限制),wait操作必须搭配锁来进行因为wait需要释放锁,但是notify操作原则上说不涉及加锁的操作但是java也是强制要求notify搭配synchronized,线程、锁都是操作系统本身支持的特性,wait和notify在操作系统中也有原生的对应的api,原生api中wait必须搭配锁来使用notify则不需要。但是我们不是在单例模式下只能实例一次的吗? 阅读详情

转载 http://www.cnblogs.com/coffee/archive/2011/12/05/inside-java-singleton.html

 关于单例模式的文章,其实网上早就已经泛滥了。但一个小小的单例,里面却是有着许多的变化。网上的文章大多也是提到了其中的一个或几个点,很少有比较全面且脉络清晰的文章,于是,我便萌生了写这篇文章的念头。企图把这个单例说透,说深入。但愿我不会做的太差。

  首先来看一个典型的实现:

复制代码
 1 /**
2 * 基础的单例模式,Lazy模式,非线程安全
3 * 优点:lazy,初次使用时实例化单例,避免资源浪费
4 * 缺点:1、lazy,如果实例初始化非常耗时,初始使用时,可能造成性能问题
5 * 2、非线程安全。多线程下可能会有多个实例被初始化。
6 *
7 * @author laichendong
8 * @since 2011-12-5
9 */
10 public class SingletonOne {
11
12 /** 单例实例变量 */
13 private static SingletonOne instance = null;
14
15 /**
16 * 私有化的构造方法,保证外部的类不能通过构造器来实例化。
17*/
18 private SingletonOne() {
19
20 }
21
22 /**
23 * 获取单例对象实例
24 *
25 * @return 单例对象
26*/
27 public static SingletonOne getInstance() {
28 if (instance == null) { // 1
29 instance = new SingletonOne(); // 2
30 }
31 return instance;
32 }
33
34 }
复制代码

  注释中已经有简单的分析了。接下来分析一下关于“非线程安全”的部分。

  1、当线程A进入到第28行(#1)时,检查instance是否为空,此时是空的。
  2、此时,线程B也进入到28行(#1)。切换到线程B执行。同样检查instance为空,于是往下执行29行(#2),创建了一个实例。接着返回了。
  3、在切换回线程A,由于之前检查到instance为空。所以也会执行29行(#2)创建实例。返回。
  4、至此,已经有两个实例被创建了,这不是我们所希望的。 

 怎么解决线程安全问题?

  方法一:同步方法。即在getInstance()方法上加上synchronized关键字。这时单例变成了  

复制代码
 1 /**
2 * copyright © sf-express Inc
3 */
4 package com.something.singleton;
5
6 /**
7 * 同步方法 的单例模式,Lazy模式,线程安全
8 * 优点:
9 * 1、lazy,初次使用时实例化单例,避免资源浪费
10 * 2、线程安全
11 * 缺点:
12 * 1、lazy,如果实例初始化非常耗时,初始使用时,可能造成性能问题
13 * 2、每次调用getInstance()都要获得同步锁,性能消耗。
14 *
15 * @author laichendong
16 * @since 2011-12-5
17 */
18 public class SingletonTwo {
19
20 /** 单例实例变量 */
21 private static SingletonTwo instance = null;
22
23 /**
24 * 私有化的构造方法,保证外部的类不能通过构造器来实例化。
25 */
26 private SingletonTwo() {
27
28 }
29
30 /**
31 * 获取单例对象实例
32 * 同步方法,实现线程互斥访问,保证线程安全。
33 *
34 * @return 单例对象
35 */
36 public static synchronized SingletonTwo getInstance() {
37 if (instance == null) { // 1
38 instance = new SingletonTwo(); // 2
39 }
40 return instance;
41 }
42
43 }
复制代码

 

  加上synchronized后确实实现了线程的互斥访问getInstance()方法。从而保证了线程安全。但是这样就完美了么?我们看。其实在典型实现里,会导致问题的只是当instance还没有被实例化的时候,多个线程访问#1的代码才会导致问题。而当instance已经实例化完成后。每次调用getInstance(),其实都是直接返回的。即使是多个线程访问,也不会出问题。但给方法加上synchronized后。所有getInstance()的调用都要同步了。其实我们只是在第一次调用的时候要同步。而同步需要消耗性能。这就是问题。

  方法二:双重检查加锁Double-checked locking。
  
其实经过分析发现,我们只要保证 instance = new SingletonOne(); 是线程互斥访问的就可以保证线程安全了。那把同步方法加以改造,只用synchronized块包裹这一句。就得到了下面的代码:

复制代码
1     public static SingletonThree getInstance() {
2 if (instance == null) { // 1
3 synchronized (SingletonThree.class) {
4 instance = new SingletonThree(); // 2
5 }
6 }
7 return instance;
8 }
复制代码

 

  这个方法可行么?分析一下发现是不行的!
  1、线程A和线程B同时进入//1的位置。这时instance是为空的。
  2、线程A进入synchronized块,创建实例,线程B等待。
  3、线程A返回,线程B继续进入synchronized块,创建实例。。。
  4、这时已经有两个实例创建了。 

  为了解决这个问题。我们需要在//2的之前,再加上一次检查instance是否被实例化。(双重检查加锁)接下来,代码变成了这样:

复制代码
 1     public static SingletonThree getInstance() {
2 if (instance == null) { // 1
3 synchronized (SingletonThree.class) {
4 if (instance == null) {
5 instance = new SingletonThree(); // 2
6 }
7 }
8 }
9 return instance;
10 }
复制代码

 

  这样,当线程A返回,线程B进入synchronized块后,会先检查一下instance实例是否被创建,这时实例已经被线程A创建过了。所以线程B不会再创建实例,而是直接返回。貌似!到此为止,这个问题已经被我们完美的解决了。遗憾的是,事实完全不是这样!这个方法在单核和 多核的cpu下都不能保证很好的工作。导致这个方法失败的原因是当前java平台的内存模型。java平台内存模型中有一个叫“无序写”(out-of-order writes)的机制。正是这个机制导致了双重检查加锁方法的失效。这个问题的关键在上面代码上的第5行:instance = new SingletonThree(); 这行其实做了两个事情:1、调用构造方法,创建了一个实例。2、把这个实例赋值给instance这个实例变量。可问题就是,这两步jvm是不保证顺序的。也就是说。可能在调用构造方法之前,instance已经被设置为非空了。下面我们看一下出问题的过程:
  1、线程A进入getInstance()方法。
  2、因为此时instance为空,所以线程A进入synchronized块。
  3、线程A执行 instance = new SingletonThree(); 把实例变量instance设置成了非空。(注意,实在调用构造方法之前。)
  4、线程A退出,线程B进入。
  5、线程B检查instance是否为空,此时不为空(第三步的时候被线程A设置成了非空)。线程B返回instance的引用。(问题出现了,这时instance的引用并不是SingletonThree的实例,因为没有调用构造方法。) 
  6、线程B退出,线程A进入。
  7、线程A继续调用构造方法,完成instance的初始化,再返回。 

  好吧,继续努力,解决由“无序写”带来的问题。

复制代码
 1     public static SingletonThree getInstance() {
2 if (instance == null) {
3 synchronized (SingletonThree.class) { // 1
4 SingletonThree temp = instance; // 2
5 if (temp == null) {
6 synchronized (SingletonThree.class) { // 3
7 temp = new SingletonThree(); // 4
8 }
9 instance = temp; // 5
10 }
11 }
12 }
13 return instance;
14 }
复制代码

 

  解释一下执行步骤。
  1、线程A进入getInstance()方法。
  2、因为instance是空的 ,所以线程A进入位置//1的第一个synchronized块。
  3、线程A执行位置//2的代码,把instance赋值给本地变量temp。instance为空,所以temp也为空。 
  4、因为temp为空,所以线程A进入位置//3的第二个synchronized块。
  5、线程A执行位置//4的代码,把temp设置成非空,但还没有调用构造方法!(“无序写”问题) 
  6、线程A阻塞,线程B进入getInstance()方法。
  7、因为instance为空,所以线程B试图进入第一个synchronized块。但由于线程A已经在里面了。所以无法进入。线程B阻塞。
  8、线程A激活,继续执行位置//4的代码。调用构造方法。生成实例。
  9、将temp的实例引用赋值给instance。退出两个synchronized块。返回实例。
  10、线程B激活,进入第一个synchronized块。
  11、线程B执行位置//2的代码,把instance实例赋值给temp本地变量。
  12、线程B判断本地变量temp不为空,所以跳过if块。返回instance实例。

  好吧,问题终于解决了,线程安全了。但是我们的代码由最初的3行代码变成了现在的一大坨~。于是又有了下面的方法。

  方法三:预先初始化static变量。

复制代码
 1 /**
2 * 预先初始化static变量 的单例模式 非Lazy 线程安全
3 * 优点:
4 * 1、线程安全
5 * 缺点:
6 * 1、非懒加载,如果构造的单例很大,构造完又迟迟不使用,会导致资源浪费。
7 *
8 * @author laichendong
9 * @since 2011-12-5
10 */
11 public class SingletonFour {
12
13 /** 单例变量 ,static的,在类加载时进行初始化一次,保证线程安全 */
14 private static SingletonFour instance = new SingletonFour();
15
16 /**
17 * 私有化的构造方法,保证外部的类不能通过构造器来实例化。
18 */
19 private SingletonFour() {
20
21 }
22
23 /**
24 * 获取单例对象实例
25 *
26 * @return 单例对象
27 */
28 public static SingletonFour getInstance() {
29 return instance;
30 }
31
32 }
复制代码

  看到这个方法,世界又变得清净了。由于java的机制,static的成员变量只在类加载的时候初始化一次,且类加载是线程安全的。所以这个方法实现的单例是线程安全的。但是这个方法却牺牲了Lazy的特性。单例类加载的时候就实例化了。如注释所述:非懒加载,如果构造的单例很大,构造完又迟迟不使用,会导致资源浪费。

  那到底有没有完美的办法?懒加载,线程安全,代码简单。

  方法四:使用内部类。

 

复制代码
 1 /**
2 * 基于内部类的单例模式 Lazy 线程安全
3 * 优点:
4 * 1、线程安全
5 * 2、lazy
6 * 缺点:
7 * 1、待发现
8 *
9 * @author laichendong
10 * @since 2011-12-5
11 */
12 public class SingletonFive {
13
14 /**
15 * 内部类,用于实现lzay机制
16 */
17 private static class SingletonHolder{
18 /** 单例变量 */
19 private static SingletonFive instance = new SingletonFive();
20 }
21
22 /**
23 * 私有化的构造方法,保证外部的类不能通过构造器来实例化。
24 */
25 private SingletonFive() {
26
27 }
28
29 /**
30 * 获取单例对象实例
31 *
32 * @return 单例对象
33 */
34 public static SingletonFive getInstance() {
35 return SingletonHolder.instance;
36 }
37
38 }
复制代码

  解释一下,因为java机制规定,内部类SingletonHolder只有在getInstance()方法第一次调用的时候才会被加载(实现了lazy),而且其加载过程是线程安全的(实现线程安全)。内部类加载的时候实例化一次instance。

 

  最后,总结一下:
  1、如果单例对象不大,允许非懒加载,可以使用方法三。
  2、如果需要懒加载,且允许一部分性能损耗,可以使用方法一。(官方说目前高版本的synchronized已经比较快了)
  3、如果需要懒加载,且不怕麻烦,可以使用方法二。
  4、如果需要懒加载,没有且!推荐使用方法四。 


《剑指offer》Java版 一、引言 《剑指offer》可谓是程序猿面试的神书了,在面试中帮了我很多,大部分面试的算法题都会遇到原题或者是类似的题。但是书上的代码都是C版的,我在这里整理了一份Java版的代码供大家学习参考,这些代码我都是在OJ上跑过全AC的,所以正确性你大可放心。 二、目录 1赋值运算函数 2单例设计模式 3二维数组中查找目标值 4替换字符串中的空格 5从尾到头打印链表 6由前序和中序遍历重建二叉树 7用两... 阅读详情

相关推荐

Java多线程--解决单例模式中的懒汉式的线程安全问题

Java多线程--解决单例模式中的懒汉式的线程安全问题

人生得意须尽欢 2595

Java多线程基础-8:单例模式及其线程安全问题

单例指的就是单个实例(instance),也就是单个对象(对象就是类的实例)。单例模式指的是某个类在进程中只有唯一一个实例(在一个程序中,只能创建一个实例(一个对象),不能创建多个对象)。按理来说,在写代码的时候多 new 几次,就能创建多个对象了。但在语法上,是有办法禁止这样多 new 几次的操作的。也就是说,Java中的单例模式,实际上是借助 Java 语法,保证某个类只能够创建出一个实例,而不能被new多次。为什么会有这样的用途?其实原因是很简单的:在有些场景下,本身它就要求某个概念是单例的。

wyd_333的博客 3315

【科研】Michaelis–Menten 曲线(米氏曲线)中反应速率的计算

Michaelis–Menten 曲线 米氏曲线 纵坐标 反应速率求值 朗伯比尔定律

weixin_42809675的博客 2万+

线程安全之单例模式

这篇文章,我们会介绍一下单例模式,但这里的单例模式,不是我们所说的设计模式,当然听到设计模式,大家一定都说,我当然知道设计模式了,有23种呢?一下子一顿输出,当然我这里说的单例模式还是跟设计模式有一些区别的,当然我不做概述,因为我也没咋个去了解过设计模式,我把大家拉回来,什么是多线程对的单例模式呢?看完我以下的解释相信你会明白的.多线程环境下的单例模式需要保证只有一个实例对象被创建,并且可以在多线程环境下安全地访问该实例。

m0_61869253的博客 1690

Cisco Packet Tracert 无线路由器配置

Cisco Packet Tracert 无线路由器配置

yushan_caoyu的博客 2万+

别再乱刷了!《另一个伊甸》异节、改典、典录获取途径完全梳理,这些隐藏奖励你拿全了吗?

本文全面解析《另一个伊甸》中异节、改典、典录的获取途径,包括断章、外传、精灵试炼等高概率掉落副本,以及隐藏任务、限时活动和特殊挑战。提供高效收集策略和优先级建议,帮助玩家快速获取这些稀缺资源,实现角色真证和上限突破。

weixin_42699679的博客 579

Java中单例模式的线程安全性

Java中单例模式的线程安全性

QYgujingjing的专栏 1211

本地部署了qwen2.5:0.8b ,对接openclaw 后,电脑卡的要死

OpenClaw 强制 16k 上下文 + 无限历史 + Agent 调度,把 0.8b 压垮。按上面配置:轻量化 + 限历史 + 驻留显存,电脑立刻不卡。

专注于技术研发行业技术主要有AI人工智能视觉识别、物联网技术、电商平台、app原生技术、微信小程序、鸿蒙语言开发、办公企业oa、ERP等,致力于做东半球最优秀的程序员 976

剑指offer--Java实现

剑指offer合辑之面试01-67 《剑指offer》面试题 Java 代码的 Github 地址 《剑指offer》面试题 Markdown 版本,包含题目、分析及代码的 Github 地址 面试题2 实现单例模式 面试题3 二维数组中的查找 面试题4 替换空格 面试题5 从尾到头打印链表 面试题6 重建二叉树 面试题7 用两个栈实现队列 面试题8 旋转数组的最小数字 面试题9 斐波那契数列...

LYZ 4109

【架构设计】DDD 到底解决了什么问题

DDD作为架构设计思想帮助微服务控制规模复杂度,那它是怎么做到的呢?第一章提出了架构的根本目是解决系统复杂度,并简要说明系统复杂度的六个来源及通用解法,为我们设计架构提供了清晰可执行的操作思路。

DreamSun的博客 1158

【QT开发教程】使用Qt进行NFC编程

近场通信(NFC,Near Field Communication)技术广泛应用于现代设备中,例如智能手机、支付终端和门禁系统。Qt提供了一套完整的NFC模块,使开发者可以轻松地在应用程序中实现NFC功能。本文将介绍如何在Qt中进行NFC编程,包括基本概念、使用方法以及一些实际的示例。

科技改变人类,技术成就未来 1465

剑指Offer第二版Java代码实现

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入 欢迎使用Ma...

凝沙的博客 1282

使用Transformer实现自动调制识别(RML2016a)

image_size = 128 # resize重置输入大小patch_size = 2 # 从输入图片(信号看作图片)中将被提取的块大小# 分成 num_patches 个块num_patches = (image_size // patch_size) #(72/6)**2 横着裁剪),],return x)(x1, x1)# MLP.# Add MLP.x=x_train,y=y_train,filepath,round。

QAQIknow的博客 6274

【剑指offer】Java版代码(完整版)

一、引言 《剑指offer》可谓是程序猿面试的神书了,在面试中帮了我很多,大部分面试的算法题都会遇到原题或者是类似的题。但是书上的代码都是C版的,我在这里整理了一份Java版的代码供大家学习参考,这些代码我都是在OJ上跑过全AC的,所以正确性你大可放心。 二、目录 赋值运算函数 单例设计模式 二维数组中查找目标值 替换字符串中的空格 从尾到头打印链表 由前序和中序遍历重建二叉树 用两个栈实现队列 ...

thatkrisyang 230

YOLO11改进:轻量级改进 | 通用倒瓶颈(UIB)搜索块结合C3k2二次创新 | 轻量化之王MobileNetV4

通用倒瓶颈(UIB)搜索块替代YOLO11的C3k2

会AI的学姐 292

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

剑指offer面试题汇总-Java实现

剑指offer面试题汇总-Java实现 说明:参考DERRANTCM的剑指offer学习专栏的排版。 DERRANTCM是我的学习榜样,他的博客给予我极大的帮助。感谢你!让我们一起分享知识,创造快乐! 目录第01-10题面试题2 实现单例模式面试题3 二维数组中的查找面试题4 替换空格面试题5 从尾到头打印链表面试题6 重建二叉树面试题6 重建二叉树2面试题7 用两个栈实现队列面试题8 旋

LYZ 1801

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

《剑指offer》最全Java版(1-20题)(长期更新)

前言 《剑指offer》总结了许多面试中常见的算法题,对于提高编程和算法很有帮助。但是原书是用c语言实现的,我参考原书并加上自己的见解整理了Java版本的。但是碍于篇幅,很多细节可能无法交代清楚,更详细的版本和总结请见我的Github。本人代码小白,还在砥砺前行,考虑必有不周,欢迎交流指正。 文章目录前言02_实现Singleton模式 02_实现Singleton模式 1、懒汉式(单线程版)[不...

Marco___的博客 1646

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

《剑指Offer》Java版目录(源码下载)

转:https://blog.youkuaiyun.com/login_sonata/article/details/77366354 《剑指Offer》PDF版下载:点我 书中全部50道题目的Java实现如下,java源码下载:点我 剑指Offer面试题1面试题2单例模式 剑指Offer面试题3二维数组查找 剑指Offer面试题4替换空格 剑指Offer面试题5反向打印链表 剑指Offer面试题6重建...

Jaylon Wang的专栏 172

剑指offer面试题2:实现单例模式-----java实现

最近计划跟着剑指offer的面试题学习一下算法。都是学习。面试题2: 设计一个类,我们只能生成该类的一个实例。我们来提供五种方法来实现:1 最简单的单例模式,但是只适合在单线程下使用(不好)package com.no2; /** * 简单单例模式(只适合单线程下使用) * * @author yushen * */ public class Singleton { // 单例模...

yushen 284

《剑指offer》66道算法题合集(java实现)

1

weixin_43774841的博客 2万+

[Java刷算法]牛客—剑指offer第一天

直面未来,挑战面试,和小编一起挑战30天刷完《牛客—剑指offer》。等你来战!!!!

muzi_longren的博客 9018

java 版剑指offer算法集锦

比如,第一天,你刷这道算法题的时候看不懂,先不用着急,很多人都是这样过来的,先搜一下答案,看一下别人是怎么解决的。第三,掌握一种算法,不仅要知道 what,还要知道 why(分析各种算法的优缺点),比如 topK问题,有常见的几种解决方案,排序,快排思想,海量数据堆排序。第二天,自己再写一遍,加深印象。第一,要了解基本的数据结果,数组,聊表,Map,Set,二叉树等,了解他们的优缺点,时间复杂度,空间复杂度等。现如今,如果你想进入大厂,腾讯,阿里,头条,拼多多等,不管是社招还是校招,肯定都会面试到算法的。

2401_89734031的博客 1910

java 版剑指offer算法集锦,java面试编程题及答案

由于篇幅限制,小编在此截出几张知识讲解的图解[外链图片转存中…(img-sIf8ITFW-1711909849838)][外链图片转存中…(img-LLssK4ha-1711909849838)][外链图片转存中…(img-ruSl3rB5-1711909849839)][外链图片转存中…(img-Aa2EoBWz-1711909849839)]

2401_84003839的博客 953

《剑指offer》66题JAVA代码算法实现全集

这篇文章是自己在做《剑指offer》过程中的记录和总结,我发现自己出错的原因大部分是因为这些代码我都是直接手打的,没有使用IDE,很多这样最能检验自己的基本功是否扎实,强推~以下这些题目提供的解法大部分都是我在答完题之后结合别人的解题思路进行了整合,同时也会贴出别人的多元化解法。所有题目的目录如下:【剑指offer——JAVA实现】二维数组中的查找【剑指offer——JAVA实现】替换空格【剑指o...

罗先森何其有幸的博客 1392
上一篇: java 快速排序 时间复杂度 空间复杂度 稳定性
下一篇: 如何判断数据中某张表是否存在
黄玮鹏
博客等级 码龄17年 262粉丝 20原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值