论坛首页 Java企业应用论坛

用happen-before规则重新审视DCL

浏览 39595 次
该帖已经被评为精华帖
作者 正文
   发表时间:2009-02-12   最后修改:2009-02-13
     呵呵,楼主的确是辛苦,你给我的解答,我也是这么想的,主动类装载始终是可见的,我写成这样就是因为看了你的帖子后不确定是否也存在这样的多线程问题,第一个主动调用而loader Test类的Thread,另外一个Thread 同时访问Test的静态变量,是否会出现 Test.class 已经非空,而static 变量static 块中附的值仍然不可见。
    我原本个人觉得class 非空的时候,应该肯定是class对象完全初始化完成,但却从来没有思考原因,就是因为仔细看了你的这篇文章后,才有了这个疑惑。
    但我仔细想想后猜想应该是这样的,classloader 类装载最终调用的是native方法,当一个主动调用Test的线程执行的时候,调用的native方法获取class文件流并获得class对象值,是C直接操作内存,它返回的class对象就已经内存可见了,它已经脱离了jvm对内存操作的指令排序,所以Test.class 被赋值的时候,class对象就内存可见了。
     不管怎么说得感谢楼主的帖子,让我有了这些思考。:)希望高手指正我的思考不对的地方,也希望这种思考对其他人有用。
0 请登录后投票
   发表时间:2009-02-13   最后修改:2009-02-13
@moshalanye:
你没必要深入细节当中,不能以“最终调用的是native方法”作为解释的理由,因为全部用java写个虚拟机是完全可能的。

在一个类没有初始化完成时,是不可能做对这个类做任何事情的(包括创建它的实例,访问静态变量和方法)。

直接引用JLS,类初始化做的事:

Initialization of a class consists of executing its static initializers and the initializers for static fields (class variables) declared in the class.


类初始化发生在什么时候?JLS这样说:
A class or interface type T will be initialized immediately before the first occurrence of any one of the following:

    * T is a class and an instance of T is created.
    * T is a class and a static method declared by T is invoked.
    * A static field declared by T is assigned.
    * A static field declared by T is used and the field is not a constant variable (§4.12.4).
    * T is a top-level class, and an assert statement (§14.10) lexically nested within T is executed.

对你这个例子来说,访问Test的静态变量必定导致类被初始化(A static field declared by T is used and the field is not a constant variable),初始化就会执行static initializers,也就是设置Test.test的值为2。一旦初始化完成,只能得到Test.test的值为2。
0 请登录后投票
   发表时间:2009-02-13  
public class TableConfig {
	//....
	private FieldConfig[] allFields;
	
	private transient FieldConfig[] _editFields;

	//....
	
	public FieldConfig[] getEditFields() {
		if (_editFields == null) {
			List<FieldConfig> editFields = new ArrayList<FieldConfig>();
			for (int i = 0; i < allFields.length; i++) {
				if (allFields[i].editable) editFields.add(allFields[i]);
			}
			_editFields = editFields.toArray(new FieldConfig[editFields.size()]);
		}
		return _editFields;
	}
}

 

lz,你的_editFields为何声明为transient??而不是volatile?

0 请登录后投票
   发表时间:2009-02-13  
marlonyao 写道
@moshalanye:
你没必要深入细节当中,不能以“最终调用的是native方法”作为解释的理由,因为全部用java写个虚拟机是完全可能的。

在一个类没有初始化完成时,是不可能做对这个类做任何事情的(包括创建它的实例,访问静态变量和方法)。

直接引用JLS,类初始化做的事:

Initialization of a class consists of executing its static initializers and the initializers for static fields (class variables) declared in the class.


类初始化发生在什么时候?JLS这样说:
A class or interface type T will be initialized immediately before the first occurrence of any one of the following:

    * T is a class and an instance of T is created.
    * T is a class and a static method declared by T is invoked.
    * A static field declared by T is assigned.
    * A static field declared by T is used and the field is not a constant variable (§4.12.4).
    * T is a top-level class, and an assert statement (§14.10) lexically nested within T is executed.

对你这个例子来说,访问Test的静态变量必定导致类被初始化(A static field declared by T is used and the field is not a constant variable),初始化就会执行static initializers,也就是设置Test.test的值为2。一旦初始化完成,只能得到Test.test的值为2。



纯粹是一种学习的思考,学习java基础时我已经明白主动调用导致主动装载,我明白什么时候会触发类装载,而你说的纯粹的使用java来写一个虚拟机,我们暂时不去思考它的可行性(用什么去操作内存,总是有依赖,不依赖C,难道不依赖汇编,不依赖os指令?)。但首先有一点,initialized immediately 是否会利用某种 方式来保证其初始化所有操作的内存可见性,而我思考native方法导致这点的实现正好是一种现存的方式。
   
  
   
   
引用
z,你的_editFields为何声明为transient??而不是volatile?

    声明为volatile的情况,lz 已经作了解答,仔细看文章就知道啊,不声明volatile 是为了展示会存在的问题。



    学习本来就是一个细致的事情,我不想在意识到问题的时候不去寻求一些可以涉及的知识,而且这个不正是学习语言而又不局限于语言的一种方式,所以在论坛上我一直都很尊重这种细致求学的javaeye们,即使被人觉得没有必要,但从长远来看,他们始终会是走在前面的人。
0 请登录后投票
   发表时间:2009-02-13   最后修改:2009-02-13
FaJa 写道
public class TableConfig {
	//....
	private FieldConfig[] allFields;
	
	private transient FieldConfig[] _editFields;

	//....
	
	public FieldConfig[] getEditFields() {
		if (_editFields == null) {
			List<FieldConfig> editFields = new ArrayList<FieldConfig>();
			for (int i = 0; i < allFields.length; i++) {
				if (allFields[i].editable) editFields.add(allFields[i]);
			}
			_editFields = editFields.toArray(new FieldConfig[editFields.size()]);
		}
		return _editFields;
	}
}

 

lz,你的_editFields为何声明为transient??而不是volatile?

 

噢,给你造成误解了,这里用transient没有特别的意思。我喜欢用transient来代表这个字段表示导出的字段(即这个字段可以由其它字段计算出来),并且我还喜欢把这样的字段名称以"_"开头。这只是我个人的风格,不过我认为这是有好处的。如果TableConfig实现了Serializable接口,那么将_editFields标识为transient绝对是明智的,不同的实现可能有或者没有这个字段。

 

也不是如moshalanye所说,是为了展示问题,这样的话我直接声明为private就可以了,另外这个项目使用的jdk还是1.4。

 

p.s. 我就是楼主,只是用了个不同的用户名,如果造成麻烦请见谅!

0 请登录后投票
   发表时间:2009-02-13  
moshalanye 写道
但首先有一点,initialized immediately 是否会利用某种 方式来保证其初始化所有操作的内存可见性,而我思考native方法导致这点的实现正好是一种现存的方式。

 用同步就可以保证所有操作的内存可见性。看一下这里吧,或许有用。

0 请登录后投票
   发表时间:2009-02-13   最后修改:2009-02-13
    楼主 :) 我知道 你用transient的原因,只是因为你这篇文章提到的是多线程问题,而不是序列化问题,所以用不用transient不是关键,说是为了展示问题应该没错麻!
    你提供的文章我会仔细看得,先谢了。用同步实现内存可见你的文章讲的就是这个,所以我也就没有必要造次,再去提这个方式了,楼主,我也是仔细的看懂了你的文章的。
  :)
0 请登录后投票
   发表时间:2009-02-16  
写的非常好。
0 请登录后投票
   发表时间:2009-03-10  
非常感谢楼主提供的文章,不过我有一个提议哦,如果楼主先讲讲reorder会更好,在文章中的HB的前提-由于JMM为了提高在多核CPU执行效率,它会对执行的代码排序,也就你说的时间上面的HB,而并且不是执行上的HB行为。

呵呵,如果我没有记错的话,在JLS说,HB是全序,而不是遍序。


PS:去年的时候,我找这样文章太久了,在整个国内,后来没有办法,只能去看Doug Lea的文章,不过还是一些同步实现不理解,希望不吝赐教哦!呵呵
0 请登录后投票
   发表时间:2009-03-10  
lifethinker 写道
大家都理解DCL了吗,还是不理解我这里讲的?



谢谢,我看过楼主提到的那文章,JDK5修正了JMM以前的不足,并且volatile和final有了“新”的语义。

不知道楼主看了Googletalk的视频没有,他谈到了一些使用volaitle的用途和使用,还有其他的方面。


这里贴出来,给大家参考!
地址:http://www.youtube.com/watch?v=1FX4zco0ziY&feature=channel_page

0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics