关于java重排序的问题

问题描述

今天看《java并发编程实战》看到重排序的地方P28,这个例子始终都没有成功,代码如下:publicclassNoVisibility{privatestaticbooleanready;privatestaticintnumber;privatestaticclassReaderThreadextendsThread{publicvoidrun(){while(!ready){Thread.yield();}System.out.println(number);}}publicstaticvoidmain(String[]args){newReaderThread().start();number=42;ready=true;}}

按作者意思,这里打印的结果很可能是0,或者根本无法终止,因为在代码中没有使用足够的同步机制,因此无法保证主线程写入的ready值和number值对于读线程来说是可见的;P28但是本人测试n次,均打印出来的是42,然后本人用1000个线程运行得到的结果也都是42,求大神解答publicclassNoVisibility{privatestaticclassReaderThreadextendsThread{privateWriteThreadwriteThread;ReaderThread(WriteThreadwt){writeThread=wt;}@Overridepublicvoidrun(){while(!writeThread.ready){Thread.yield();}System.out.println(writeThread.number);}}privatestaticclassWriteThreadextendsThread{publicbooleanready;publicintnumber;@Overridepublicvoidrun(){number=42;ready=true;}}publicstaticvoidmain(String[]args){for(inti=0;i<1000;i++){WriteThreadwt=newWriteThread();newReaderThread(wt).start();wt.start();}}}

解决方案

本帖最后由 A289048093 于 2014-02-24 17:13:44 编辑
解决方案二:
你根本没有改变number的地方,当然不变了
解决方案三:
莫非这是一个错误的错误例子
解决方案四:
引用楼主A289048093的回复:

今天看《java并发编程实战》看到重排序的地方P28,这个例子始终都没有成功,代码如下:publicclassNoVisibility{privatestaticbooleanready;privatestaticintnumber;privatestaticclassReaderThreadextendsThread{publicvoidrun(){while(!ready){Thread.yield();}System.out.println(number);}}publicstaticvoidmain(String[]args){newReaderThread().start();number=42;ready=true;}}

按作者意思,这里打印的结果很可能是0,或者根本无法终止,因为在代码中没有使用足够的同步机制,因此无法保证主线程写入的ready值和number值对于读线程来说是可见的;P28但是本人测试n次,均打印出来的是42,然后本人用1000个线程运行得到的结果也都是42,求大神解答publicclassNoVisibility{privatestaticclassReaderThreadextendsThread{privateWriteThreadwriteThread;ReaderThread(WriteThreadwt){writeThread=wt;}@Overridepublicvoidrun(){while(!writeThread.ready){Thread.yield();}System.out.println(writeThread.number);}}privatestaticclassWriteThreadextendsThread{publicbooleanready;publicintnumber;@Overridepublicvoidrun(){number=42;ready=true;}}publicstaticvoidmain(String[]args){for(inti=0;i<1000;i++){WriteThreadwt=newWriteThread();newReaderThread(wt).start();wt.start();}}}

main方法里面有number=42啊,就这里赋值了,原文作者的意思是“编译器、处理器以及运行时都可能对操作的执行顺序进行一些意向不到的调整”,就是说可能在运行的时候ReaderThread读到ready=true的时候,可能还没读到number=42这里(因为可能存在重排序问题,ReadThread读到的结果是先ready=true,这里就开始运行打印操作了,读到的值是初始值(0),然后读到number=42),这样导致打印结果可能是0,但是我没能得到这个结果。
解决方案五:
引用1楼lwb314的回复:

你根本没有改变number的地方,当然不变了

main方法里面有number=42赋值了,原文作者的意思是“编译器、处理器以及运行时都可能对操作的执行顺序进行一些意向不到的调整”,就是说可能在运行的时候ReaderThread读到ready=true的时候,可能还没读到number=42这里(因为可能存在重排序问题,ReadThread读到的结果是先ready=true,这里就开始运行打印操作了,读到的值是初始值(0),然后读到number=42),这样导致打印结果可能是0,但是我没能得到这个结果。
解决方案六:
引用4楼A289048093的回复:

Quote: 引用1楼lwb314的回复:
你根本没有改变number的地方,当然不变了

main方法里面有number=42赋值了,原文作者的意思是“编译器、处理器以及运行时都可能对操作的执行顺序进行一些意向不到的调整”,就是说可能在运行的时候ReaderThread读到ready=true的时候,可能还没读到number=42这里(因为可能存在重排序问题,ReadThread读到的结果是先ready=true,这里就开始运行打印操作了,读到的值是初始值(0),然后读到number=42),这样导致打印结果可能是0,但是我没能得到这个结果。

他说的编译器优化,可能是指两个赋值同步进行导致,应该是非常极端的情况
解决方案七:
这个问题我也试过了,没有得到想要的结果,后来想想未必作者说的是错的,作者说的出现的状况本来就是极端的情况,至于是否能出现这个状况,要看重排序的结果,以及线程调度机制的时间片段分配,这几个短短的操作其实才3毫秒左右。privatestaticclasssonThreadextendsThread{publicvoidrun(){while(!ready){System.out.println("交出控制权时间:"+System.currentTimeMillis());Thread.yield();}System.out.println(number);System.out.println("结束时间:"+System.currentTimeMillis());}}publicstaticvoidmain(String[]args){newsonThread().start();number=42;System.out.println("number赋值时间:"+System.currentTimeMillis());ready=true;System.out.println("reder赋值时间:"+System.currentTimeMillis());}这是我想看的程序运行轨迹number赋值时间:1418089495926reder赋值时间:1418089495929交出控制权时间:141808949592642结束时间:1418089495931
解决方案八:

解决方案九:
我觉得书上这么说是在表达一种意思,如果没有设置同步的话可能线程A运行到while语句时->线程Breadey-true而现在ready-true,而你仍然会运行yield()...不设置同步的话就不知道到底执行的那个方法...但是我搞不懂的是,为什么会可能出现ready一直为false的情况了,都是指向同一块内存...
解决方案十:
书上说的没错,由于线程调度和重排序的原因,main函数中各个语句以及主线程与其他线程的执行顺序是不确定的。所以作者说的情况的确是有可能发生的。但是主线程在被调度器交换下来之前极有可能number=42;ready=true;两个语句全部执行完了,然后结果就是每次打印42.number=42;ready=true;这两处修改在线程里面有可能不可见,也就是说主线程修改了自己的调用栈里面的这两个值,而没有将其刷新到主存。其他线程也没有进行副本的重新拷贝,这样就有可能导致死循环。这里表现出来的好像两个变量是volatile类型一样,这一点我也不理解。
解决方案十一:
重排列基于CPU,编译器的各种不同选项。一般我们也不具备各种cpu,编译器,编译器选项的测试条件,这个重排列很难测出来。
解决方案十二:
你用的是不是client模式,client模式不会冲排序的你试试用-server启动,可能就有效果了
解决方案十三:
你看下9楼的说法吧,书上想表述的就是这个原理。如果你看了重排序的资料就应该明白。
解决方案十四:
这个和CPU有关,而且发生的几率非常低,还是不要试了。。。
解决方案十五:
main也是一个线程,主线程一般先运行的,等于number=42,ready=true,你想让主线程慢点执行,你得用jion吧。
解决方案:
http://bbs.csdn.net/topics/390757750已经在上面的帖子回复过了,复制一份:首先类加载会初始化ready和number的值分别为false和0,可能会出现的情况应该有4种,楼主执行的结果是一种,剩下的3种情况应该是这样的:1.无限循环,number的值为0:在主线程即main方法中对ready的设置(即ready=true)还没来得及写回主存(静态变量保存在方法区),ReaderThread线程就已经读取了ready的值(并保留了副本),然后加载到Java栈中,此时ready一直为false所以出现死循环。number的值也可以类似推理,在主线程即main方法中对number的设置(即number=42)还没来得及写回主存(静态变量保存在方法区),ReaderThread线程就已经读取了number的值(并保留了副本),然后加载到Java栈中,此时number一直为0(只是没有打印出来而已);2.无限循环,number的值为42:在主线程即main方法中对ready的设置(即ready=true)还没来得及写回主存(静态变量保存在方法区),ReaderThread线程就已经读取了ready的值(并保留了副本),然后加载到Java栈中,此时ready一直为false所以出现死循环。在主线程即main方法中对number的设置(即number=42)后(即number的值已经写回了主存),ReaderThread线程才开始执行此时读取的number为42(只是没有打印出来而已);3.输出0:在主线程即main方法中对ready的设置(即ready=true)后(即ready的值已经写回了主存),还没来得及写回主存(静态变量保存在方法区),ReaderThread线程就已经读取了number的值(并保留了副本),然后加载到Java栈中,此时number为0;至于为什么会出现ready=true写回主存后,number=42还没写回主存。这应该是由于Java虚拟机的一种优化技术叫指令重排序,number=42不一定会在ready=true前面执行,得看Java虚拟机是怎么优化的。

时间: 2024-12-02 22:46:59

关于java重排序的问题的相关文章

深入理解Java内存模型(二) 重排序

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依 赖性.数据依赖分下列三种类型: 上 面三种情况,只要重排序两个操作的执行顺序,程序的执行结果将会被改变. 前面提到过,编译 器和处理器可能会对操作做重排序.编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不 会改变存在数据依赖关系的两个操作的执行顺序. 注意,这里所说的数据依赖性仅针对单个处理 器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器 和处理器考

Java内存模型FAQ(四)重排序意味着什么?

原文:http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html 第四章 译者:Alex 在很多情况下,访问一个程序变量(对象实例字段,类静态字段和数组元素)可能会使用不同的顺序执行,而不是程序语义所指定的顺序执行.编译器能够自由的以优化的名义去改变指令顺序.在特定的环境下,处理器可能会次序颠倒的执行指令.数据可能在寄存器,处理器缓冲区和主内存中以不同的次序移动,而不是按照程序指定的顺序. 例如,如果一个线程写入值到字段a,然后写入

Java内存访问重排序笔记

关于重排序 重排序通常是编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段. 重排序分为两类:编译期重排序和运行期重排序,分别对应编译时和运行时环境. As-if-serial语义 as-if-serial语义的意思是,所有的动作(Action)都可以为了优化而被重排序,但是必须保证它们重排序后的结果和程序代码本身的应有结果是一致的. Java编译器.运行时和处理器都会保证单线程下的as-if-serial语义. 比如,为了保证这一语义,重排序不会发生在有数据依赖的操作之

Java并发编程系列之三:重排序与顺序一致性

前言 在我们编写程序并运行的时候,编译器给我们一个错觉:程序编译的顺序与编写的顺序是一致的.但是实际上,为了提高性能,编译器和处理器常常会对指令进行重排序.重排序主要分为两类:编译器优化的重排序.指令级别并行的重排序和内存系统的重排序.所以我们编写好Java源代码之后,会经过以上三个重排序,到最终的指令序列.我们这里提到的Java内存模型又是什么呢?Java内存模型(后面简称JMM)是语言级别的内存模型,主要用于控制一个共享变量的写入何时对另一个线程可见(后面所有方面都是围绕这点展开的).JMM

深入理解Java内存模型(二)——重排序

本文属于作者原创,原文发表于InfoQ:http://www.infoq.com/cn/articles/java-memory-model-2 数据依赖性 如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性.数据依赖分下列三种类型: 名称 代码示例 说明 写后读 a = 1;b = a; 写一个变量之后,再读这个位置. 写后写 a = 1;a = 2; 写一个变量之后,再写这个变量. 读后写 a = b;b = 1; 读一个变量之后,再写这个变量. 上

JVM的重排序

感谢同事[沐剑]的投稿 重排序通常是编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段.重排序分为两类:编译期重排序和运行期重排序,分别对应编译时和运行时环境. 在并发程序中,程序员会特别关注不同进程或线程之间的数据同步,特别是多个线程同时修改同一变量时,必须采取可靠的同步或其它措施保障数据被正确地修改,这里的一条重要原则是:不要假设指令执行的顺序,你无法预知不同线程之间的指令会以何种顺序执行. 但是在单线程程序中,通常我们容易假设指令是顺序执行的,否则可以想象程序会发生

java-eclipse中的debug和指令重排序

问题描述 eclipse中的debug和指令重排序 Java代码经过优化会有指令重排序,那么eclipse的debug按照代码从上到下的顺序执行又是如何做到的 解决方案 debug的程序都是不做优化的,编译的时候使用的是javac -g,除了优化之外,还有assert只在debug的时候才会执行.其实这不是eclipse这样,其他ide也一样 解决方案二: debug按照代码从上到下的顺序执行,是因为debug模式下的代码是未经过优化的. 你不设置断点,debug和Release运行对比一下.

2个java希尔排序示例_java

java希尔排序 希尔排序是插入排序的一种类型,也可以用一个形象的叫法缩小增量法.基本思想就是把一个数组分为好几个数组,有点像分治法,不过这里的划分是用一个常量d来控制. 这个0<d<n,n为数组的长度.这个算法有了插入排序的速度,也可以算是一个改进算法,在插入算法中,如果有一个最小的数在数组的最后面,用插入算法就会重最后一个 位置移动到第一个,这样就会浪费很大,使用这个改进的希尔排序可以实现数据元素的大跨度的移动.也就是这个算法的优越之处. 复制代码 代码如下: package cn.cqu

指令重排序会破坏happens-before原则吗

问题描述 今天阅读"深入理解java虚拟机"时,P333,关于happens-before解释,有这么一段:引用 解决方案 引用线程A: readConfig(); //读取配置 init=true; 线程B: while(init){ useConfig(); //使用配置 } 由于线程A可能会发生指令重排序,所以线程B使用的配置可能尚未加载,所以使用volatile解决此问题. 比如说,在readConfig();里有N多的指令要执行指令abcdinit=true;如果abcd和i