黑马程序员技术交流社区
标题:
【成都校区】关于单例的一些小笔记分享
[打印本页]
作者:
Morn明
时间:
2018-11-29 13:57
标题:
【成都校区】关于单例的一些小笔记分享
package
尝试.DanLiThreadSafe;
//Thread [θred]
线程
/*public class Singleton2 {
private Singleton2(){
}
private static Singleton2 instance;
public static Singleton2 getInstance(){
if(instance == null) {//1
:读取
instance
的值
instance = new Singleton2();//2:
实例化
instance
}
return instance;
}
}
*/
/*
对于以上代码注释部分,如果此时有两个线程,线程
A
执行到
1
处,读取了
instance
为
null
,
然后
cpu
就被线程
B
抢去了,此时,线程
A
还没有对
instance
进行实例化。
因此,线程
B
读取
instance
时仍然为
null
,于是,它对
instance
进行实例化了。
然后,
cpu
就被线程
A
抢去了。此时,线程
A
由于已经读取了
instance
的值并且认为它为
null
,所以,
再次对
instance
进行实例化。所以,线程
A
和线程
B
返回的不是同一个实例。
那么,如何解决呢?
1.
在方法前面加
synchronized
修饰。这样肯定不会再有线程安全问题。
*/
/*public class Singleton2 {
private Singleton2(){
}
private static Singleton2 instance;
public static synchronized Singleton2 getInstance(){
if(instance == null) {//1
instance = new Singleton2();//2
}
return instance;
}
}*/
/*
但是,这种解决方式,假如有
100
个线程同时执行,那么,每次去执行
getInstance
方法时都要先获得锁再去执行方法体,
如果没有锁,就要等待,耗时长,感觉像是变成了串行处理。因此,尝试其他更好的处理方式。
*/
/*2.
加同步代码块,减少锁的颗粒大小。我们发现,只有第一次
instance
为
null
的时候,才去创建实例,
而判断
instance
是否为
null
是读的操作,不可能存在线程安全问题,因此,
我们只需要对创建实例的代码进行同步代码块的处理,
也就是所谓的对可能出现线程安全的代码进行同步代码块的处理。
*/
/*public class Singleton2 {
private Singleton2(){
}
private static Singleton2 instance;
public static Singleton2 getInstance(){
if(instance == null) {
synchronized (Singleton2.class){
instance = new Singleton2();
}
}
return instance;
}
}*/
/*
但是,这样处理就没有问题了吗?同样的原理,线程
A
和线程
B
,线程
A
读取
instance
值为
null
,
此时
cpu
被线程
B
抢去了,线程
B
再来判断
instance
值为
null
,于是,它开始执行同步代码块中的代码,
对
instance
进行实例化。此时,线程
A
获得
cpu
,由于线程
A
之前已经判断
instance
值为
null
,
于是开始执行它后面的同步代码块代码。它也会去对
instance
进行实例化。
这样就导致了还是会创建两个不一样的实例。
那么,如何解决上面的问题。
很简单,在同步代码块中
instance
实例化之前进行判断,如果
instance
为
null
,才对其进行实例化。
这样,就能保证
instance
只会实例化一次了。也就是所谓的双重检查加锁机制。
再次分析上面的场景:
线程
A
和线程
B
,线程
A
读取
instance
值为
null
,此时
cpu
被线程
B
抢去了,线程
B
再来判断
instance
值为
null
。
于是,它开始执行同步代码块代码,对
instance
进行了实例化。这是线程
A
获得
cpu
执行权,
当线程
A
去执行同步代码块中的代码时,它再去判断
instance
的值,由于线程
B
执行完后已经将这个共享资源
instance
实例化了,
所以
instance
不再为
null
,所以,线程
A
就不会再次实行实例化代码了。
*/
/*public class Singleton2 {
private Singleton2(){
}
private static Singleton2 instance;
public static synchronized Singleton2 getInstance(){
if(instance == null) {
synchronized (Singleton2.class){
if (instance == null){
instance = new Singleton2();
}
}
}
return instance;
}
}*/
/*
但是,双重检查加锁并不代码百分百一定没有线程安全问题了。因为,这里会涉及到一个指令重排序问题。
instance = new Singleton2()
其实可以分为下面的步骤:
1.
申请一块内存空间;
2.
在这块空间里实例化对象;
3.instance
的引用指向这块空间地址;
指令重排序存在的问题是:
对于以上步骤,指令重排序很有可能不是按上面
123
步骤依次执行的。比如,
先执行
1
申请一块内存空间,然后执行
3
步骤,
instance
的引用去指向刚刚申请的内存空间地址,
那么,当它再去执行
2
步骤,判断
instance
时,由于
instance
已经指向了某一地址,它就不会再为
null
了,
因此,也就不会实例化对象了。这就是所谓的指令重排序安全问题。那么,如何解决这个问题呢?
加上
volatile
关键字,因为
volatile
可以禁止指令重排序。
volatile [
ˈ
v
ɒ
l
ə
ta
ɪ
l]
易变的,不稳定的
;
*/
public class
Singleton2 {
private
Singleton2() {
}
private static volatile
Singleton2
instance
;
public static
Singleton2 getInstance() {
if
(
instance
==
null
) {
synchronized
(Singleton2.
class
) {
if
(
instance
==
null
) {
instance
=
new
Singleton2();
}
}
}
return
instance
;
}
}
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/)
黑马程序员IT技术论坛 X3.2