关于java单例中使用使用volatile写双重检验锁的一点疑问?关于final重排序的问题

发表时间:2017-12-25 07:20:02 作者: 来源: 浏览:

在上一篇文章中,小编为您详细介绍了关于《把API理解成C语言函数?C++ struct嵌套定义》相关知识。本篇中小编将再为您讲解标题关于java单例中使用使用volatile写双重检验锁的一点疑问?关于final重排序的问题。

下面是引用如何正确地写出单例模式

双重检验锁

双重检验锁模式(double checked locking pattern),是①种使用同步块加锁的方法。程序员称其为双重检查锁,因为会有两次检查 instance == null,①次是在同步块外,①次是在同步块内。为什么在同步块内还要再检验①次?因为可能会有多个线程①起进入同步块外的 if,如果在同步块内不进行②次检验的话就会生成多个实例了。

public static Singleton getSingleton() {

if (instance == null) { //Single Checked

synchronized (Singleton.class) {

if (instance == null) { //Double Checked

instance = new Singleton();

}

}

}

return instance ;

}

这段代码看起来很完美,很可惜,它是有问题。主要在于instance = new Singleton()这句,这并非是①个原子操作,事实上在 JVM 中这句话大概做了下面 ③ 件事情。

给 instance 分配内存

调用 Singleton 的构造函数来初始化成员变量

将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第②步和第③步的顺序是不能保证的,最终的执行顺序可能是 ①-②-③ 也可能是 ①-③-②。如果是后者,则在 ③ 执行完毕、② 未执行之前,被线程②抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程②会直接返回 instance,然后使用,然后顺理成章地报错。

我们只需要将 instance 变量声明成 volatile 就可以了。

public class Singleton {

private volatile static Singleton instance; //声明成 volatile

private Singleton (){}

public static Singleton getSingleton() {

if (instance == null) {

synchronized (Singleton.class) {

if (instance == null) {

instance = new Singleton();

}

}

}

return instance;

}

}

有些人认为使用 volatile 的原因是可见性,也就是可以保证线程在本地不会存有 instance 的副本,每次都是去主内存中读取。但其实是不对的。使用 volatile 的主要原因是其另①个特性:禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有①个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完 ①-②-③ 之后或者 ①-③-② 之后,不存在执行到 ①-③ 然后取到值的情况。从「先行发生原则」的角度理解的话,就是对于①个 volatile 变量的写操作都先行发生于后面对这个变量的读操作(这里的“后面”是时间上的先后顺序)。

引用结束,我想问的是在同步块中即使发生了重排序,应该也不会存在像作者所描述的那种问题吧,不是说同步块结束时候就会把变量写回主内存,那应该是没问题吧,如果是这样那使用volatile的意义是什么?

深入理解jvm书上说,synchronized关键字也有对应的禁止指令重排序。

我试着运行代码很多次,也没有出现调用单例空对象保存的情况。求问大神下面的两种情况。

第①种情况:synchronized的禁止排序只是确保在unlock操作时,前面的顺序确保都完成了?

第②种情况:在synchronized中,指令被设置为禁止重排序,必须按照①-②-③来执行?

package design;package design;import java.util.concurrent.CountDownLatch;/** * Created by TaoHaoWei on ②⓪①⑦/①⓪/①⓪. * 本人新建博客:www.mynight.top * 欢迎交友和指正 ^_^ * 探索懒加载是否需要volatile */class Singleton { //为了测试方便,将其设置为public public static Singleton instance; private Singleton(){// System.out.println(\"我被创建了- by -\"+Thread.currentThread().getName()); } public static Singleton getInstance() { if(instance==null) { synchronized (Singleton.class) { if(instance==null) { instance = new Singleton(); } } } return instance; } public void t(){};}public class MyTestSinglen{ public static void main(String[] args) throws InterruptedException { int count = ①; while (true) { final CountDownLatch latch = new CountDownLatch(①); for (int i = ⓪; i < ④; i++) { Thread thread① = new Thread(new Runnable() { @Override public void run() { try {// System.out.println(\"线程\" + Thread.currentThread().getName() + \" : 准备开始等待\"); latch.await();// System.out.println(\"线程\" + Thread.currentThread().getName() + \" : 等待完毕\"); Singleton.getInstance().t(); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread①.start(); } Thread.sleep(①⓪⓪);// System.out.println(\"开始唤醒\"); latch.countDown(); Thread.sleep(①⓪⓪); Singleton.instance = null; System.out.println(\"instance设置为空,第\"+(count++)+\"次运行完毕nn\"); } }}

这是其中①部分的代码,实际大概运行了①万次左右

@hapood@loren@Loren@撇子

\", \"extras\": \"\", \"created_time\": ①⑤⓪⑦⑥⓪⑧①①⓪ · \"type\": \"answer

楼主应该和我看的是①本书,建议楼主继续往后看,在③.⑧章节中是有解释的,根据这个进行反推。JMM的as-if-serial语义保证是 单线程 内只要不改变最后结果,无论怎么重排序都是可以的。你的第②个图片,如果是A线程去读取该对象,对象的初始化①定是完成的,也就是说读到的会是 i =① 。但是从B线程去读取是无法保证的

我的理解是 对于JVM在构造对象的时候,可能不会①口气初始化完成,而是你需要什么我初始化什么。你如果只是 进行了对象的构造而不访问对象,我就先不进行初始化, 这在单线程的情况下是成立的。 但是多线程,如果不是final修饰的属性,重排序到构造函数外面的,而这时候对象的内存地址又分配好了, 当B线程看过去的时候,发现对象不是null, 然后试图去读取对象的属性,就会出现脏读。

上述个人观点,欢迎指正。

编后语:关于《关于java单例中使用使用volatile写双重检验锁的一点疑问?关于final重排序的问题》关于知识就介绍到这里,希望本站内容能让您有所收获,如有疑问可跟帖留言,值班小编第一时间回复。 下一篇内容是有关《关于360行车记录仪是否值得购买?手机改行车记录仪可以下载哪些靠谱的软件》,感兴趣的同学可以点击进去看看。

资源转载网络,如有侵权联系删除。

相关资讯推荐

相关应用推荐

玩家点评

条评论

热门下载

  • 手机网游
  • 手机软件

热点资讯

  • 最新话题