volatile 的问题。

wangbin0016 2010-08-27 02:42:16
volatile 据说是 实现 可见 非互斥的行为。。。

下面是书上的一个例子 。。



public class Testvolatile {

private static boolean flag ;

public static void main(String []str) throws InterruptedException
{
Thread back = new Thread(new Runnable()
{
public void run(){
int i = 0;
while(!flag)
System.out.println(i++);

}
}) ;
back.start();
Thread.sleep(1000);
flag = true;
}


}


理论上 应该这个程序一直执行下去 , 因为 主线程修改了flag = true 。但是 线程back应该看不到 。。

要把flag 设置为volatile 才可以看到 。。。



但是我运行了 这个程序 ,发现不需要volatile 的关键字 ,程序依然会停止 。
何解???

...全文
216 20 打赏 收藏 转发到动态 举报
写回复
用AI写文章
20 条回复
切换为时间正序
请发表友善的回复…
发表回复
thegodofwar 2010-08-31
  • 打赏
  • 举报
回复
[Quote=引用 18 楼 wangbin0016 的回复:]
我在HotSpot VM server上面运行 ,结果也是一样 的 。。。


我看了一下java的内存模型 , 应该是线程运行的时候 会清空线程内存 ,把主内存最新的数据读取出来。。那么如果是主要 ,子线程就应该能读到 flag的值。 只是可能不是立刻而已 。但对于这个程序没有影响。

感谢楼上几位兄弟的解释 。。。 这里我只是疑问Volatile 这个修饰的作用而已。。。这样看来 V……
[/Quote]
LZ相信自己的眼睛,这个程序绝对不会一直运行下去,有可能是书印刷出错了O(∩_∩)O~
wangbin0016 2010-08-31
  • 打赏
  • 举报
回复
我在HotSpot VM server上面运行 ,结果也是一样 的 。。。


我看了一下java的内存模型 , 应该是线程运行的时候 会清空线程内存 ,把主内存最新的数据读取出来。。那么如果是主要 ,子线程就应该能读到 flag的值。 只是可能不是立刻而已 。但对于这个程序没有影响。

感谢楼上几位兄弟的解释 。。。 这里我只是疑问Volatile 这个修饰的作用而已。。。这样看来 Volatile 这个经典的例子事实上没什么意义了 。。。。就是在dcl里面 在java5之后用他可以避免出错了。

关键是effective java 和 java core 两本权威的书的解释Volatile 这样看来都是错误的 。。。


特别是effective java 里面写到 不用Volatile ,他的机器将会永远执行下去 。就是上面的这个例子。。。。。。我一直不理解呀 ,难道真的是 他搞错了 ,他既然说他的机器运行下去,难道没测试过,为什么他测试 就会这样呢??
rainsilence 2010-08-31
  • 打赏
  • 举报
回复
你的代码,虽然是一个现成读,一个现程写,但是并没有对线程冲突那么敏感。

volatile 保证了在写入后,或者写入中的第一时间你就能取到最新值,因为它是另外一种形式的同步。但是并不是说,你不加volatile ,java就不会把它更新成最新值。只是一个时间的问题,而不是结果的问题。结果是你总能取到最新值
wangbin0016 2010-08-30
  • 打赏
  • 举报
回复
楼上的几位兄弟说 ,一个线程改变了 另外一个线程 可以看到分别是 1秒或者100秒的事情 。但是这个程序我执行了很多次,基本上都是立刻看到的 。可能误差不过是几毫秒。

子线程是在不停的判断 flag = 什么 但是 不是一个在同一个线程改变的。。。他有可能看到 。这个不是一本书里面说的 ,core java 里面也有。

还有有位兄弟说:“难道你在一边写完一个变量 再访问的时候难道永远看不到 那java完了 彻底完了 我不用编程了 我死了算了”。。。

一边写变量 另外访问可以看到 ,这个 是在一个线程内 理论上是这样的。 而且我们大多数都是一个线程内用到的。多线程的时候如果同步了,也是可以看到的 。。。


这里是指volatile 的用法 。。

在core java里面 是这样说的。。。。。volatile 关键字为实例域的同步访问提高了一种免锁机制。如果声明一个域为volatile 那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。(换句话是不是应该理解为 如果不用volatile 那么编译器就不知道他会被另一个线程并发更新,或者是不能立刻知道,否则用volatile 这个有什么必要。)


h47966392 2010-08-30
  • 打赏
  • 举报
回复
再摘取一段话给你 Volatile的实现同步的原理 :
 Volatile 比同步更简单,只适合于控制对基本变量(整数、布尔变量等)的单个实例的访问。当一个变量被声明成 volatile,任何对该变量的写操作都会绕过高速缓存,直接写入主内存,而任何对该变量的读取也都绕过高速缓存,直接取自主内存。这表示所有线程在任何时候看到的 volatile 变量值都相同。
h47966392 2010-08-30
  • 打赏
  • 举报
回复
楼主问的问题是不是
这个flag加volatile 2个线程才能同步到新值
不加volatile那就不应该同步到新值了 你要问的是不是这个???????

如果是 你这里有2个线程同时访问同一个变量flag , 一个是修改 一个是读取
你子线程先执行读取flag操作 一直读取一直打印 后来主线程flag=true设置了新值 那么子线程就应该获取到flag的新值true 只是时间问题(这个时间问题后面再说)
你前面说的 一边写变量 另外访问可以看到 ,这个 是在一个线程内 理论上是这样的。 而且我们大多数都是一个线程内用到的。多线程的时候如果同步了,也是可以看到的 。。。
你意思就是说不同步了 那么就获取不到新值了吗? 那是错的 , 你就算不同步 也能获取到新值 你2个线程访问的都是同一个flag
我转一段话给你
 Java的内存模型分为主存储区和工作存储区。主存储区保存了Java中所有的实例。也就是说,在我们使用new来建立一个对象后,这个对象及它内部的方法、变量等都保存在这一区域,在MyThread类中的n就保存在这个区域。主存储区可以被所有线程共享。而工作存储区就是我们前面所讲的线程栈,在这个区域里保存了在run方法以及run方法所调用的方法中定义的变量,也就是方法变量。在线程要修改主存储区中的变量时,并不是直接修改这些变量,而是将它们先复制到当前线程的工作存储区,在修改完后,再将这个变量值覆盖主存储区的相应的变量值。----这里就有时间差异了,你这个例子这个时间差异是很小 你另外写一个例子建立几百个线程应该可以看的到时间差异了

加volatile关键字使用同步的意思是什么呢? 就是为了让你在1个线程改变了值 其他线程获取到当前变量的新值的时候是"立马"获取到 同理 你自己去加同步锁也是一样的 也就是说让这2个线程共享同一个内存视图 一端改变 另外一端"立马"获取到
acmilan0825 2010-08-30
  • 打赏
  • 举报
回复
来看看,不大明白啊
thegodofwar 2010-08-30
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 wangbin0016 的回复:]
楼上的几位兄弟说 ,一个线程改变了 另外一个线程 可以看到分别是 1秒或者100秒的事情 。但是这个程序我执行了很多次,基本上都是立刻看到的 。可能误差不过是几毫秒。

子线程是在不停的判断 flag = 什么 但是 不是一个在同一个线程改变的。。。他有可能看到 。这个不是一本书里面说的 ,core java 里面也有。

还有有位兄弟说:“难道你在一边写完一个变量 再访问的时候难道永远看……
[/Quote]
说那么多LZ还是不明白,你根本没好好看我给你在三楼的解释,这个问题暂时不谈volatile(实际当中这个关键字用的极少),不要什么都书上怎么说怎么说的那种背教条,你自己多测试测试不就明白了,你把
Thread.sleep(1000);  
改成
Thread.sleep(600000); 
运行看看数字绝对要打印6分钟左右,然后程序结束;还有你把
Thread.sleep(1000);  
这句代码去掉看看是不是什么也不会打印...怎么那么简单的东西你会那么坚决的认为flag=true不会被运行,I 服了 you!
thegodofwar 2010-08-29
  • 打赏
  • 举报
回复
纳闷,这跟那个VM版本有what关系( ⊙ o ⊙ )?这个又不是bug...
h47966392 2010-08-28
  • 打赏
  • 举报
回复
你加不加 这个关键字 这个线程都会关闭的
这个例子说明了什么呢? 我到现在也不清楚

volatile的意思是可见的 意思就是说一个线程改变了这个值 别的线程"立马"就能看的到
你不加这个关键字 那么一个线程改变了这个值 别的线程"不能立马"就能看的到 也有可能一秒或者几百年才看的到 也就是说第一个线程改变了值 另外一个线程读取到的可能还是旧的数据
michaellufhl 2010-08-28
  • 打赏
  • 举报
回复
您的那个例子来自是Effective Java的‘Synchronize access to shared mutable data’,我看到书里面也指出了这是个optimization是来自HotSpot Server。

把代码优化成:
if(!flag)
while(true)
i++;

Bloch也只是说这是‘quite acceptable’:)
wangbin0016 2010-08-28
  • 打赏
  • 举报
回复
回复3楼的 。。 这代码是Effective Java这本书的一个测试例子 。。 也是很多volatile 介绍的一个例子。。。 volatile一个作用是 代替 synchronized 的关键字 ,在非互斥情况下。。。。一个线程写多个线程读的时候 。 实现多线程的可见。。。 这里面正好是一个线程读,另一个线程写。所以理论上按照介绍应该是需要volatile关键字的 。。主线程 执行flag =TRUE之后,虽然执行完了 ,但是另外一个线程依然应该执行 ,因为他没有看到flag 的值的改变。



还有5楼的, 你说是vm版本的问题 没看到 书上写到过。。。。难道vm版本会让 java代码语义发生变化吗。。。。那这样 对于java的到处运行不是起到影响了 ,,,为什么要有2个版本呢 。。他们的区别和意义在哪里呢?
michaellufhl 2010-08-28
  • 打赏
  • 举报
回复
根本问题是HotSpot VM的类型

HotSpot VM的类型有2种:client 和 server。
您书上的例子一定是运行在sever版本上,而您自己的测试版本在client上。
client版的实现没有作优化(hoisting)所以没有永不跳出循环的问题。

ps:您可以
System.out.println(System.getProperty("java.vm.name"));
察看当前VM版本。
michaellufhl 2010-08-28
  • 打赏
  • 举报
回复
[Quote=引用 10 楼 h47966392 的回复:]


难道你在一边写完一个变量 再访问的时候难道永远看不到 那java完了 彻底完了 我不用编程了 我死了算了
……
[/Quote]
VM的版本的差异导致thread执行的不同不是我说的,是Joshua Bloch说的,我觉得他比我们都权威:)
h47966392 2010-08-28
  • 打赏
  • 举报
回复
楼主说: 主线程 执行flag =TRUE之后,虽然执行完了 ,但是另外一个线程依然应该执行 ,因为他没有看到flag 的值的改变。

我就想问他怎么会看不到?????????
我的回答是 : 一定看的到新的值 只是看到这个新值的时间是1秒还是100年 因素有很多

难道你在一边写完一个变量 再访问的时候难道永远看不到 那java完了 彻底完了 我不用编程了 我死了算了

5楼说的研究过头了吧 这根VM有关系吗? 这又让我想死了
thegodofwar 2010-08-28
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 wangbin0016 的回复:]
回复3楼的 。。 这代码是Effective Java这本书的一个测试例子 。。 也是很多volatile 介绍的一个例子。。。 volatile一个作用是 代替 synchronized 的关键字 ,在非互斥情况下。。。。一个线程写多个线程读的时候 。 实现多线程的可见。。。 这里面正好是一个线程读,另一个线程写。所以理论上按照介绍应该是需要volatile关键字的 。。主线程 执行flag =……
[/Quote]
你为什么那么肯定
主线程执行flag =TRUE之后,虽然执行完了 ,但是另外一个线程依然应该执行 ,因为他没有看到flag 的值的改变
子线程看不到呀( ⊙ o ⊙ )?晕...因为子线程在不断循环判断flag是不是为false,子线程执行时怎么会看不到flag为true呢?( ⊙ o ⊙ )!
thegodofwar 2010-08-27
  • 打赏
  • 举报
回复
不知道你明白没有,理论上这个程序一定会自动结束~~建议改一下这个测试代码,不会的再说~~~ O(∩_∩)O~
thegodofwar 2010-08-27
  • 打赏
  • 举报
回复
这个问题主要是由于这句代码造成的
Thread.sleep(1000);  
,你发现没有你把这句话写在了主线程里,并且是第一句,所以程序一定是最新执行
Thread.sleep(1000);  
这句,当CPU发现需要等待1秒时,它会把执行权交给子线程(该程序中就一个主线程,一个子线程),所以它会执行
int i = 0;    while(!flag) 
System.out.println(i++);
;因为默认的flag是false,所以必然会执行打印那句代码,当执行了一会(差不多过了1秒,此时已打印了n行,计算机速度好快\(^o^)/~),cpu转而接着执行主线程
flag = true;
,此时主线程结束,子线程因为flag=true了也执行结束....
所以就这样,根本无需volatile方法程序自动结束,归根到底是你本身测试方法写的有问题
xuxianyue 2010-08-27
  • 打赏
  • 举报
回复
当然可以不需要,只是这里做不到主线程和back 线程同步而已。
wangbin0016 2010-08-27
  • 打赏
  • 举报
回复
没人懂吗?

62,614

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧