问题描述
今天看《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虚拟机是怎么优化的。