u9平台是真的吗怎么玩,稳定安全吗?

可选中1个或多个下面的关键词,搜索相关资料。也可直接点“搜索资料”搜索整个问题。

就是在这上面玩的,没有出现什么意外的情况

??熟悉设计模式的朋友应该都知道单例模式,这里不再对单例模式的基础进行介绍,本文重点在于解释为什么双重检查没有达到真正意义上的线程安全,当然也要介绍怎么达到真正的线程安全。
??本文的知识点主要来源:

??1.方腾飞、魏鹏、程晓明三位老师的《Java 并发编程的艺术》
??2.周志明老师的《深入理解Java虚拟机》

??大家都知道,单例模式主要分为:懒汉模式和饿汉模式。当我们在使用单例模式时,考虑到延迟加载,懒汉模式肯定是必须的。但是懒汉模式有一个很大的缺点,那就是线程不安全。我们为了解决这个问题,发明了双重检查锁定的写法,如下:

??如上代码所示,如果第一次检查instnace不为null的话,那么就不需要去获取锁来进行instance的初始化。因此,看上去可以大大的降低synchronized带来的性能开销。
??双重检查锁定看上去非常的完美,但是却是一个错误的优化。当一个线程在执行到1时,读取到instance不为null时,instance引用的对象可能还没有初始化完毕。

??在前面的代码中,instance = new SingleTon();是用来创建对象。这一行代码可以分解为如下三行伪代码:

??因为2和3不存在数据依赖性,所以可能会被重排序。2和3重排序之后的执行顺讯可能如下:

??这里可能有人对重排序存在疑惑,如果想要理解什么是重排序,为什么要重排序等等原因,强烈推荐:方腾飞、魏鹏、程晓明三位老师的《Java 并发编程的艺术》。这里我就不对这部分进行展开,主要是自己太菜了,害怕对这部分的解释不好。
??我们知道instance = new SingleTon()这一步可能会被重排序之后,现在我们来看看什么情况下能够导致问题。假设有两个线程,ThreadA和ThreadB,这两个线程都在调用SingleTone的getInstance方法来获取一个SingleTon的对象。执行顺序可能出现如下情况:
semantics保证所有的重排序在单线程里内,程序的执行结果不会被改变)。但是当ThreadB在按照上图的顺序在执行时,ThreadB将看到一个还没有被初始化的SingleTon对象。

A1:分配对象的内存空间

??上表就是对上面的流程图的一个总结。我们知道,最后ThreadB可能会返回一个为未初始化的对象。
??在知道了问题的根源之后,我们可以想出两个办法来实现线程安全的延迟初始化。
??1.不允许2和3重排序。
??2.允许2和3重排序,但是不允许其他线程“看到”这个重排序。

??前面解释了双重检查锁定问题的根源,并且列出了两种解决思路。这里,我们将对这两种思路进行展开。

??这个解决方案是非常的简单,只需要将我们之前的那个instance变量使用volatile关键字来修饰就行了。如下:

??是不是非常的简单?当然我们这里的目的当然不是简单的实现解决方案,而是详细的解释为什么需要这样做。

??由于instance是volatile变量,所以上面的代码中3相当于是对volatile变量进行写的操作,也就是所谓的volatile写。根据《Java 并发编程的艺术》的P43,我们知道对于一个volatile写,编译器会在volatile写的前面加入一个StoreStore内存屏障,用来防止前面的普通写与下面的volatile写进行重排序;在volatile写的后面加入一个StoreLoad屏障,主要是防止上面的volatile写与下面的可能有的volatile读/写进行重排序。如下图:
??所以,我们可以得出,只要instance被volatile修饰了,2和3就不能重排序。可以得出新的时序图:
??这样,我们通过上面解决方案中第一个方案来保证了线程安全的延迟加载。

(3).基于类初始化的解决方案

??我们知道,一个类的加载主要分为4步:验证、准备、解析、初始化。为了保证类的初始化(4步中的第四步)能够成功进行,JVM在初始化阶段进行多线程同步,也就是同一个时候,只能有一个线程来对一个类进行初始化。当然,我们还知道,一个类只能被初始化一次。
??基于这个特性,可以实现另一种线程安全的延迟加载的方案。代码如下:

??当两个线程同时调用getInstance方法来获取instance对象时,如果此时InstanceHolder类还没有加载的话,首先会先加载这个类。
??这里简单的介绍一下,一个类加载的时机:
??1.首次创建该类的对象。
??2.访问该类的静态属性或者静态方法。
??所以当ThreadA和ThreadB首次获取instance对象时,InstanceHolder还没有加载,此时两个线程需要加载InstanceHolder类。由于类的加载是只能被一个线程执行,其他线程只能被阻塞住,所以当一个类被一个线程加载完毕之后,instance肯定是被初始化完毕了的,所以所有的线程看到的都是同一个instance对象。
??根据《Java 并发编程的艺术》一书中的介绍,类的初始化分为5个阶段:

A.第1阶段:通过在Class对象上的同步(即获取Class对象的初始化锁),来控制类或者接口的初始化。这个获取锁的线程会一直等待,知道线程能够获取这个初始化锁。

??我们假设,Class对象还没有被初始化(初始化状态为state,此时被标记为state = noInitialization),并且此时有两个线程(ThreadA和ThreadB)来尝试同时初始化这个对象。执行流程如下表:

A1:尝试获取Class对象的初始化锁。这里假设ThreadA获取到了初始化锁 B1:尝试获取Class对象的初始化锁,由于ThreadA获取到了初始化,所以ThreadB将一直在等待获取初始化锁
A3:线程A释放初始化锁
A1:执行类的静态初始化和初始化类中声明的静态字段
D.第4阶段:ThreadB结束类的初始化处理
B4:ThreadB的类初始化处理过程完成
E.第5阶段:ThreadC执行类的初始化的处理。
C4:ThreadC的类初始化处理过程完成

??这就是基于类初始化的解决方案的基本思路。从这里,我们可以看出来尽管instance = new SingleTon()被重排序,但是由于InstnaceHolder的初始化只能被一个线程执行,所以这里的重排序不会影响最终结果。当一个线程初始化操作完毕之后,其他的程线程看到instance对象肯定是一个初始化完毕的对象。

可选中1个或多个下面的关键词,搜索相关资料。也可直接点“搜索资料”搜索整个问题。

当然了,就在这上面玩的,信誉稳定各方面都不用担心的呢

我要回帖

更多关于 u9平台 的文章

 

随机推荐