有如下代码 private volatile int hc=0;

@Override
public int hashCode()
{
int result=hc;
if(result==0)
{
result=17;
result=result+something.hashCode();
.....
hc=result;
}
return result;
}这里的volitale有什么意义?
不是说读写 除long和double以外的基本类型 都是原子操作吗,那么也不存在hc=result时,写了一半被另一个线程读走的可能啊。

解决方案 »

  1.   

    他的操作是原子的,但是要保持多个线程对共享数据的可见性, volatile 修饰还是很有必要的,尤其是在没有同步的情况下。
      

  2.   

    volatile只是保证读写的可见性,假如,
    if(result==0)
            {
                result=17;
                result=result+something.hashCode();
                .....
                hc=result;
           
     }
    这段代码里的result统一都用hc代替,那么在运算到 ..... 部分时,恰好另外一个线程来读取 hc 的值,那么hc的值就是一个垃圾值,没有任意意义的值
      

  3.   

    什么叫“这段代码里的result统一都用hc代替”
      

  4.   

    嗯,首先纠正一下,“读写除long和double以外的基本类型都是原子操作”并不完全正确,尽管这种说法来自SUN(现在应该是Oracle了……)自己的Java入门教材:The Java Tutorial。只能说,在限定的几种32位平台(x86-32+Windows、x86-32+linux、x86-32+solaris、sparc32+solaris)的SUN虚拟机实现的特定版本上面,读写小于等于处理器字长(32位)的基本数据类型的操作是原子操作。可以预测,在64位的平台上,对long类型的操作也将成为原子操作。不过,在非常多的嵌入式系统(可能只有16位)上面,这个经验规律就是不适用的。而只能在特定平台上使用的话,无疑是对Java的“编写一次,编译一次,到处运行”的一种讽刺。而且,这种依赖平台的规律对程序员的要求比较高,非常难驾驭,所以使用volatile和java.util.concurrent.atomic包几乎总是更好的选择。volatile还有另一个作用,上面各楼努力想表达出这个意思,这个作用就是保持共享数据在各个线程的一致性。在Java中,每个线程都可能有对同一数据的本地缓存,当一个线程更改了自己的本地缓存,还没有来得及在各个线程之间同步的时候,如果另一个线程读取了那个线程自己的本地缓存——呃,悲剧就发生了。在本题中表现出来的情况就是,如果没有volatile,那么某个线程执行hashCode函数,当执行了hc=result语句之后,该线程本地缓存里的hc被更改,但是其他所有线程的本地缓存中的hc都没变还是0,在这一瞬间另一个线程也调用了hashCode并执行到了result=hc,result的值就是0了。不过本题显然整个程序块都应该同步,写成这样才是王道:
    private volatile int hc = 0; // volatile的目的是且仅是保证hc=result是原子操作public int hashCode() {
        if (hc == 0) {
            synchronize (this) {
                if (hc == 0) {
                    result = ...
                    ...
                    hc = result; // 必须是原子操作
                }
            }
        }
        return hc;
    }
      

  5.   

    java虚拟机规范中对long和double的规定都是必须占两个字长,其它诸如int,byte,reference等都占一个字长,而不管这个字究竟有多长,也就是说,一个字起码要32位,如果是64位或者128位,long和double也得占两个字长!所以,不要想当然!jvm规范鼓励jvm实现者实现long和double的原子操作,但不是强求的
      

  6.   

    java虚拟机规范中对long和double的规定都是必须占两个字长,其它诸如int,byte,reference等都占一个字长,而不管这个字究竟有多长,也就是说,一个字起码要32位,如果是64位或者128位,long和double也得占两个字长!所以,不要想当然!jvm规范鼓励jvm实现者实现long和double的原子操作,但不是强求的
      

  7.   

    java虚拟机规范中对long和double的规定都是必须占两个字长,其它诸如int,byte,reference等都占一个字长,而不管这个字究竟有多长,也就是说,一个字起码要32位,如果是64位或者128位,long和double也得占两个字长!所以,不要想当然!jvm规范鼓励jvm实现者实现long和double的原子操作,但不是强求的
      

  8.   

    如果使用 sychronize ,会使程序的并发性受到影响,但又要使线程间的共享数据同步,所以 使用 volatile 是一个折中的方法,他使得不用强制使用 sychronize ,共享数据也是同步,最新的。
      

  9.   

    晕……你再仔细读一读Java Language Specification:17.4 Nonatomic Treatment of double and longIf a double or long variable is not declared volatile, then for the purposes of load, store, read, and write actions they are treated as if they were two variables of 32 bits each: wherever the rules require one of these actions, two such actions are performed, one for each 32-bit half. The manner in which the 64 bits of a double or long variable are encoded into two 32-bit quantities is implementation-dependent. The load, store, read, and write actions on volatile variables are atomic, even if the type of the variable is double or long.This matters only because a read or write of a double or long variable may be handled by an actual main memory as two 32-bit read or write actions that may be separated in time, with other actions coming between them. Consequently, if two threads concurrently assign distinct values to the same shared non-volatile double or long variable, a subsequent use of that variable may obtain a value that is not equal to either of the assigned values, but some implementation-dependent mixture of the two values.An implementation is free to implement load, store, read, and write actions for double and long values as atomic 64-bit actions; in fact, this is strongly encouraged. The model divides them into 32-bit halves for the sake of several currently popular microprocessors that fail to provide efficient atomic memory transactions on 64-bit quantities. It would have been simpler to define all memory transactions on single variables as atomic; this more complex definition is a pragmatic concession to current hardware practice. In the future this concession may be eliminated. Meanwhile, programmers are cautioned always to explicitly synchronize access to shared double and long variables.
      

  10.   

    我觉得 hashCode 应该是只用 key 来计算 hash,对于非 key 的 field 不应该使用,hashCode 方法本身也是设计用来迎合 HashMap / HashSet 之类的集合处理 API。而使用非 key 字段来参与 hashCode 计算过程本身不合理;如果我们某种需求需要计算出某个对象当前的状态值与另一个对象是否在逻辑意义上完全相同的话,我们可以考虑新增另一个方法而不是改写 hashCode。另外,使用 key 的 field 也不应该在运行时随时变化。我们可以考虑把 key 当作构造方法的参数来强制约束的贯彻执行,而不要给那些需要强制执行的“不变约束”很多的可能性去越过这个检查的机会;这在大型软件中经常会出现不同的人(特别是公司的一个产品经过几批员工接手维护之后)对一个模块理解的不同而产生很多隐藏的错误。我就看过到很多程序员对一个 Class 或某个方法是有状态的还是无状态的不明白它们之间的差别,写的方法经常的混乱导致很多本来可以轻松的避免的或由编译器就可以检查出来的错误。
      

  11.   

    也就是的说,对于 hashCode 方法概念上讲应该是不需要使用锁的才对。
      

  12.   

    楼主的程序确实是一种很经典的求hashCode的方法。事实上,Java自身的代码就是这样写的,比如java.lang.String类的源代码:    /** Cache the hash code for the string */
        private int hash; // Default to 0    public int hashCode() {
            int h = hash;
            if (h == 0) {
                int off = offset;
                char val[] = value;
                int len = count;            for (int i = 0; i < len; i++) {
                    h = 31*h + val[off++];
                }
                hash = h;
            }
            return h;
        }
    代码的作者知道hash = h的原子性,所以声明变量hash的时候没有加volatile关键字;而由于在这个问题里,几个线程多次计算hash的值也没有什么大不了的,所以根本没有加锁。
      

  13.   

    非常感谢您的解答!!!这么晚还不睡。。result的值等于0之后,下一句便是如果resul==0,重新算哈希。还是能算出正确值。
    没什么大不了的啊。
    求解答。
      

  14.   

    很少用volitale这个修饰符 我觉得这个不应该用在原始类型上 应该用在对象类型上LZ还真是喜欢研究这些东西阿。。
      

  15.   

    没事。不过要注意 hc = result 这一句利用了原子性。
      

  16.   

    是你再好好读读话说英文不行还是读中文版比较合适,免得误人误己:An implementation is free(自由的,表示可以实现也可以不实现) to implement load, store, read, and write actions for double and long values as atomic 64-bit actions; in fact, this is strongly encouraged(只是鼓励实现者这么做)The model divides them into 32-bit halves(该模型将他们他们分成两个32位) for the sake of several currently popular microprocessors that fail to provide efficient atomic memory transactions on 64-bit quantities. It would have been(注意这个虚拟语气!) simpler to define all memory transactions on single variables as atomic; this more complex definition is a pragmatic concession to current hardware practice. In the future this concession may be eliminated. Meanwhile, programmers are cautioned always to explicitly synchronize access to shared double and long variables.
    该好好读读的地方我都标记出来了,读不懂就问一个英文好点的或者读中文版,不能想当然
      

  17.   

    晕……我讨厌人身攻击……英语和汉语都不是只凭字面解释和语法分析就能理解文章的意思的,还是我来解释吧:double和long在Java中被定义为64位,而现在的平台很多是32位,The model divides them into 32-bit halves for the sake of several currently popular microprocessors that fail to provide efficient atomic memory transactions on 64-bit quantities,所以它们在内存读写时被当成两个32位来操作,这样就略显复杂,也丧失了原子性。现在已经是2010年了,仅仅AMD64就有10年的历史了,市面上PC机也几乎已经买不到32位CPU了。It would have been simpler to define all memory transactions on single variables as atomic,64位平台可以直接传送64位数据,所以现在的传送已经可以变得很简单了,这才是这个完成时虚拟语气的含义。An implementation is free to implement load, store, read, and write actions for double and long values as atomic 64-bit actions; in fact, this is strongly encouraged,这分为两种情况,在64位平台上已经可以直接传送了,非要将其实现为两个32位传送无疑是很傻瓜的;在32位平台上,如果实现为atomic,既提供了64位数据与其他类型数据的一致性,又提供了与64位平台的兼容性,皆大欢喜。不过现在还有许多操作系统还是32位,所以64位CPU只能当作32位CPU使用,所以现在的平台还是很多是32位的。this more complex definition(64位拆成两个32位传送)is a pragmatic concession to current hardware practice. In the future this concession may be eliminated。期待64位普及的那一天。标准从来没有说过:标准倒是说过,short是严格的16位,int是严格的32位,long为64位;标准更没有说过:
      

  18.   

    顺便说一下,Java Language Specification在没有任何提示的情况下更新了,虽然上面显示的还是第三版……贴出改动后的这一节给大家看:17.7 Non-atomic Treatment of double and longSome implementations may find it convenient to divide a single write action on a 64-bit long or double value into two write actions on adjacent 32 bit values. For efficiency's sake, this behavior is implementation specific; Java virtual machines are free to perform writes to long and double values atomically or in two parts.
    For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64 bit value from one write, and the second 32 bits from another write. Writes and reads of volatile long and double values are always atomic. Writes to and reads of references are always atomic, regardless of whether they are implemented as 32 or 64 bit values.VM implementors are encouraged to avoid splitting their 64-bit values where possible. Programmers are encouraged to declare shared 64-bit values as volatile or synchronize their programs correctly to avoid possible complications.