close
The Wayback Machine - https://web.archive.org/web/20201013054128/https://github.com/Snailclimb/JavaGuide/issues/671
Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

懒汉式和饿汉式单例模式并没有很大区别 #671

Open
guang19 opened this issue Mar 5, 2020 · 15 comments
Open

懒汉式和饿汉式单例模式并没有很大区别 #671

guang19 opened this issue Mar 5, 2020 · 15 comments

Comments

@guang19
Copy link
Contributor

@guang19 guang19 commented Mar 5, 2020

先把2中单例模式的代码贴上:

饿汉式:
饿汉式单例

懒汉式:
懒汉式单例

懒汉式的单例初始化时间在第一次调用的时候。

然后关于饿汉式的初始化时间,网上大部分传闻说: “饿汉式单例在类加载阶段就已经初始化了,典型的空间换时间...”。

首先痛斥下以讹传讹的前辈们,这并不是开发者的精神。

类的生命周期我给作者提过issue,再复习一下:加载,验证,准备,解析,初始化,使用,卸载。
我想问问前辈们,饿汉式的单例在以上哪个阶段被初始化了?哪里占内存空间了?

以我个人的理解:最多在准备阶段,会给类的静态字段的变量赋零值,引用类型为null。

然后就是那8个初始化时机会初始化饿汉式的单例。而8个时机里面,正常使用,只有第一次获取单例的时候才会初始化单例。

所以我说 饿汉式与懒汉式基本无二。

@Snailclimb
Copy link
Owner

@Snailclimb Snailclimb commented Mar 6, 2020

我理解的是饿汉式和懒汉式区别点在于懒汉式在我们的单例对象没有被使用的时候不会将其加载进内,仅此而已,个人觉得两者还是很大区别的。如果说没有区别的话,可能会让很多人有误解。

@guang19
Copy link
Contributor Author

@guang19 guang19 commented Mar 6, 2020

老哥可能还是没明白我说的意思,老哥之见:懒汉式在类加载阶段不会初始化单例。敢问一句,难道饿汉式会在类加载阶段初始化单例吗?当然不会。

我上面说了那么多,其实总结一句话就是: 懒汉式和饿汉式的单例都只会在遇到new,putstatic,getstatic等指令时初始化单例。

//eager属性只在类初始化时才会被初始化
private static final Eager eager = new Eager();

image

以上。

@guang19
Copy link
Contributor Author

@guang19 guang19 commented Mar 6, 2020

我提出这个issue的本意是希望各位同学不要盲目随大流,忽略单例模式类加载和初始化的时机。
懒汉式较饿汉式实现复杂,充其量当个面试题吧。。。 还不如饿汉式实现简单。

@jinyahuan
Copy link
Contributor

@jinyahuan jinyahuan commented Mar 6, 2020

@guang19 DCL这玩意就是个面试题,考察知识点而已(很多--)。正常情况下最好使用饿汉模式。

这两个最大的区别其实就是资源的预分配,static 字段应该是在 准备 阶段就把 链接 地址给变量了(不太确定,想要明确答案的话去 jvm 规范里找),所以饿汉模式只要加载过类,他就会初始化static变量(如果后面根本不会使用他也会分配内存),而懒汉模式需要在调用(也就是有需求使用时)才分配内存,而且也有一次并发问题。

注:类加载的方式有很多种,不一定时真正使用时才加载,比如说:Class.forName("");

@guang19
Copy link
Contributor Author

@guang19 guang19 commented Mar 6, 2020

@jinyahuan 请先弄明白加载和初始化的区别,准备阶段是为static字段初始化零值(0,0L,null),初始化阶段才是赋值(执行new Eager()),而第一次调用的时候才会初始化。在我看来,你的说法与网上大部分传闻的说法没啥区别。。

@jinyahuan
Copy link
Contributor

@jinyahuan jinyahuan commented Mar 6, 2020

自己看jvm规范,static字段是在 解析阶段 设置链接地址的
这段后面补上:static final (基础数据类型变量)在“准备”阶段初始化值,引用类型在“初始化”阶段初始化值。

@jinyahuan
Copy link
Contributor

@jinyahuan jinyahuan commented Mar 6, 2020

验证方法上面都说了:Class.forName(""); 自己试一下就理解了。

@guang19
Copy link
Contributor Author

@guang19 guang19 commented Mar 6, 2020

@jinyahuan 你说的解析是将常量池中的符号引用解析为直接引用。

这是 懒汉式反编译后的常量池:
image

这是饿汉式反编译后的常量池:
image

你只看到了lazy 和 eager是常量,有符号引用,但并没有看到它们的值。

因为 
private static final Eager eager = new Eager(); 
不同于
private static final String s = "abc";

s 是字面量没错,

lazy 和 eager是常量也没错,但是new Eager() 并不能在类加载阶段就确定 eager 的内存。
只有程序运行时,也就是第一次初始化时才会生成实例。

你所说的 Class.forname() 不过是初始化类8中情况的一种情况而已,Class.forname() 会加载并初始化类。我已经说的很清楚了,

private static final Eager eager = new Eager(); 

只在初始化类时会执行,那么也就会执行new Eager()。

但我已经说过了,普通情况下,jvm第一次执行getstatic指令才会初始化类,有什么毛病吗?因此我说它们两基本无二又有什么毛病?

@guang19
Copy link
Contributor Author

@guang19 guang19 commented Mar 6, 2020

我的意思说的很清楚:懒汉式和饿汉式普通使用(不使用其它加载或获取单例的手段)没区别,并且是在1楼就说清楚了。

言尽于此吧,我想我已经说的够清楚了,无需再争论下去了。

@AnhTom2000
Copy link

@AnhTom2000 AnhTom2000 commented Mar 6, 2020

学习了,感谢

@jinyahuan
Copy link
Contributor

@jinyahuan jinyahuan commented Mar 6, 2020

@guang19 很感谢纠正了我错误的知识点 static final 修饰的引用类型确实是在初始化阶段才执行的。至于懒汉饿汉我也不想多说,懂了就是懂了,不懂的以后可能就懂了。

@Snailclimb
Copy link
Owner

@Snailclimb Snailclimb commented Mar 11, 2020

学习了。

@wangpeipei90
Copy link

@wangpeipei90 wangpeipei90 commented Jun 12, 2020

@guang19 驳斥一下你的观点。
你所说的两种单例模式没有区别的证据是两者单例对象生成的时间均为第一次加载的时候,并没有考虑多线程的环境。第二种单例模式的由来主要是因为多线程环境和指令重排的问题。

懒汉模式并没有考虑到多线程下A初始化单例对象后对B的可视化问题,也不能解决new Eager()时先得到索引,后分配空间的问题

@guankang1314
Copy link

@guankang1314 guankang1314 commented Aug 17, 2020

类初始化之后还有对象实例化啊,饿汉懒汉应该是指的对象实例化的时机。

@wangpeipei90
Copy link

@wangpeipei90 wangpeipei90 commented Aug 20, 2020

@guankang1314 还是觉得题目的意思是两种设计模式实现之间的区别。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
6 participants
You can’t perform that action at this time.