一个类暴露出的借口只有一个Object(int[] 可以看做是一个Object),我当时是用写加锁,读不加锁的方式。class A {
private  int[]   values = new int[10];
private  ReentrantLock    lock = new ReentrantLock();
public    int[]  get_all() { return values; }
public    void  update() {
    lock.lock();
    try {
        new_values = new int[11];  values = new_values;
    } finally {
        lock.unlock();
    }
}
}
后来觉得在values = new_values这一步,涉及到一个Object的赋值,可能不是原子操作,所以改为了class A {
private  int[]   values = new int[10];
private  ReentrantReadWriteLock  lock = new ReentrantReadWriteLock();
public    int[]  get_all() {
    lock.readLock().lock();
    try {
        return values;
    } finally {
        lock.readLock().unlock();
    }
}
public    void  update() {
    lock.writeLock().lock();
    try {
        new_values = new int[11];  values = new_values;
    } finally {
        lock.writeLock().unlock();
    }
}
}
请问我这样考虑是否正确?有什么效率更高的方法吗?
在C++中,我可以将结果封装在一个指针里,直接传递回一个指针,而指针的赋值是可以保证原子性的。
我对java不太熟悉,有人说Object的native实现里,可能包含了多个变量,所以赋值不可能是原子的。
也有人说,可以用AtomicReference,我不知道怎样写,谁能给个示例吗?谢谢!

解决方案 »

  1.   

    1.基本类型,引用类型的赋值,引用是原子的操作;
    2.long与double的赋值,引用是可以分割的,非原子操作;
    3.要在线程间共享long或double的字段时,必须在synchronized中操作,或是声明成volatile.所以,你这种赋值,不需要锁。
      

  2.   

    谢谢1楼,请问哪里有比较官方的介绍吗?是否是说values = new_values;实际已经改变了values所指向的内存地址,而不是将new_values里面的东西赋值给values了?我看AtomicLong里面的实现,用了volatile long。可是volatile能够保证原子性吗?我只知道volatile可以避免cpu cache引起的错误,能够保证每次都操作内存。
      

  3.   

    请问哪里有比较官方的介绍吗?
    ——看看Thinking In Java吧,这书确实不错。否是说values = new_values;实际已经改变了values所指向的内存地址,而不是将new_values里面的东西赋值给values了?
    ——这是必然的,你所期望的赋值,只有原始类型才有,对象全都是引用。我看AtomicLong里面的实现,用了volatile long。
    ——这是因为long和double,超出32位,也就是高位和低位的赋值会分为两条语句执行;所任默认情况下就无法实现原子性了可是volatile能够保证原子性吗?我只知道volatile可以避免cpu cache引起的错误,能够保证每次都操作内存。
    ——两种能力都提供了,或者说因为volatile要提供可见性,所以也就必须保证其原子性
      

  4.   

    我一个同事在jsr133上摘抄了这一段话:Write 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.再次感谢ldh911 !!
      

  5.   

    读的时候也是要加锁的不管赋值是不是原子,values是非volatile的,当a线程给其赋值后,b线程未必可见,也就说b线程看到的可能还是老的值
      

  6.   

    谢谢楼上的提醒,你说的这个是可见性的问题。确实,如果对可见性要求很高的话,确实需要加volatile,不过在我的应用里对及时性要求并不是很高,所以可能可以权衡一下,毕竟volatile效率较低。之前讨论的是原子操作,对long或double加上volatile可以保证存取是原子的。
      

  7.   

    并发度和取舍问题。如果并发频密,读取到老数据的概率就会很高。其实volatile的开销相比synchronized,低很多的。
      

  8.   

    如果只是你提供的代码,只需要把values 定义成volatile即可,get和update都不需要同步锁。不管是32位还是64位jvm,都必需保证volatile变量的取值和赋值是原子操作。
      

  9.   

    http://en.wikipedia.org/wiki/Volatile_variableThe Java programming language also has the volatile keyword, but it is used for a somewhat different purpose. When applied to a field, the Java volatile guarantees that:    (In all versions of Java) There is a global ordering on the reads and writes to a volatile variable. This implies that every thread accessing a volatile field will read its current value before continuing, instead of (potentially) using a cached value. (However, there is no guarantee about the relative ordering of volatile reads and writes with regular reads and writes, meaning that it's generally not a useful threading construct.)
        (In Java 5 or later) Volatile reads and writes establish a happens-before relationship, much like acquiring and releasing a mutex.[8]Using volatile may be faster than a lock, but it will not work in some situations.[citation needed] The range of situations in which volatile is effective was expanded in Java 5; in particular, double-checked locking now works correctly.[9]引用類型可能是由於引用只需一步操作,所以原子
      

  10.   

    可见性 可能并不是你想象的那么简单,它可能永远不可见!如下代码:
    public class Visibility {
    private  static boolean stop;

    public static void main(String[] args) throws Exception {
    new Thread(new Runnable(){
    public void run() {
    int i = 0;
    while(!stop) {
    i++;
    }
    System.out.println("finish loop,i=" + i);
    }
    }).start();


    Thread.sleep(1000);
    stop = true;
    Thread.sleep(2000);
    System.out.println("finish main");
    }
    }
    在hotspot虚拟机上这样执行: java -server Visibility 会死循环,循环的那个线程永远也看不到stop值被修改过了
      

  11.   

        JLS 17.7 Non-atomic Treatment of double and long    Some 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.jls中是這樣寫的,但如果在32位jvm上讀寫64位引用,是如何保證原子的呢
    可能是jls強制jvm實現的內部原子化操作
      

  12.   


    够狠,不带任何io和yield()之类的高密度计算,是个很不错的例子。
      

  13.   

    同对并发有浓厚兴趣,刚建的个群,java 并发:229609257,欢迎加入欢迎所有对java concurrency感兴趣的人
      

  14.   

    回12楼,你说的这个例子我前两天刚试过。也明白volatile的重要性了。另外发现一个特性就是,在循环内打印这个变量,有时候可以退出这个循环。我不太清楚是否jvm觉得打印很耗时间,就没必要cache了。其实我的任务环境是“请求-回复”的线程池应用,一个线程并不会做这样大量的密集运算,可能执行少量操作就释放了这个线程。后来我发现既然不频繁读取volatile变量,那也就无所谓效率低了,所以后来我决定还是在相关数据前都加上volatile。再次感谢!
      

  15.   

    另外问一下,线程在回归线程池后,又被取出执行另一个任务之前,会释放之前所有的Cache变量吗?我是觉得是释放的,但是不知道对不对。
      

  16.   


    cache在哪里?线程对象么?new ThreadPool的时候带了ThreadFactory参数?如果是这样的话,就有看线程池的策略了Executors类大部分new出来的线程池都是ThreadPoolExecutor类的对象(有个别不是),ThreadPoolExecutor类的构造方法传了很多参数,灵活控制了线程池的行为。至于线程会不会被回收,就要看传入的参数了,具体含义可以ThreadPoolExecutor的javadoc中找到
      

  17.   

    打印后會有不同表現,可能與while循環內部是被優化過的有關public class Visibility extends java.lang.Object{
    public Visibility();
      Code:
       0:   aload_0
       1:   invokespecial   #2; //Method java/lang/Object."<init>":()V
       4:   returnpublic static void main(java.lang.String[])   throws java.lang.Exception;
      Code:
       0:   new     #3; //class java/lang/Thread
       3:   dup
       4:   new     #4; //class Visibility$1
       7:   dup
       8:   invokespecial   #5; //Method Visibility$1."<init>":()V
       11:  invokespecial   #6; //Method java/lang/Thread."<init>":(Ljava/lang/Runna
    ble;)V
       14:  invokevirtual   #7; //Method java/lang/Thread.start:()V
       17:  ldc2_w  #8; //long 1000l
       20:  invokestatic    #10; //Method java/lang/Thread.sleep:(J)V
       23:  iconst_1
       24:  putstatic       #1; //Field stop:Z
       27:  ldc2_w  #11; //long 2000l
       30:  invokestatic    #10; //Method java/lang/Thread.sleep:(J)V
       33:  getstatic       #13; //Field java/lang/System.out:Ljava/io/PrintStream;
       36:  ldc     #14; //String finish main
       38:  invokevirtual   #15; //Method java/io/PrintStream.println:(Ljava/lang/St
    ring;)V
       41:  returnstatic boolean access$000();
      Code:
       0:   getstatic       #1; //Field stop:Z
       3:   ireturn}public class Visibility {
        private  static boolean stop;
        
        public static void main(String[] args) throws Exception {
            new Thread(new Runnable(){
                public void run() {
                    int i = 0;
                    while(!stop) {
                        i++;
                        System.out.println("stop=" + stop);
                    }
                    System.out.println("finish loop,i=" + i);
                }
            }).start();
            
            
            Thread.sleep(1000);
            stop = true;
            Thread.sleep(2000);
            System.out.println("finish main");
        }
    }
      

  18.   

    volatile的重要不重要并不要紧要紧的是,解决共享数据的可见性问题,避免数据争用,避免静态条件。多个线程访问同一个变量,若至少有一个是写操作,那么就一定要同步(或synchronized,或volatile,或java类库中的其它任何机制)才能保证线程安全
      

  19.   


    其实那个例子不加volatile在java -client Visibility的时候也不会有问题
    问题在于-server的时候虚拟机会去优化,而他什么条件下会优化,怎么去优化,我们根本无法预测
      

  20.   

    再驗證一下,看一下純的while循環public class Test {
        private  static boolean stop;
        
        public static void main(String[] args) throws Exception {
            
            while(!stop) {
                System.out.println("stop=" + stop);
            }
                   
        }
    }public class Test extends java.lang.Object{
    public Test();
      Code:
       0:   aload_0
       1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
       4:   returnpublic static void main(java.lang.String[])   throws java.lang.Exception;
      Code:
       0:   getstatic       #2; //Field stop:Z
       3:   ifne    36
       6:   getstatic       #3; //Field java/lang/System.out:Ljava/io/PrintStream;
       9:   new     #4; //class java/lang/StringBuilder
       12:  dup
       13:  invokespecial   #5; //Method java/lang/StringBuilder."<init>":()V
       16:  ldc     #6; //String stop=
       18:  invokevirtual   #7; //Method java/lang/StringBuilder.append:(Ljava/lang/
    String;)Ljava/lang/StringBuilder;
       21:  getstatic       #2; //Field stop:Z
       24:  invokevirtual   #8; //Method java/lang/StringBuilder.append:(Z)Ljava/lan
    g/StringBuilder;
       27:  invokevirtual   #9; //Method java/lang/StringBuilder.toString:()Ljava/la
    ng/String;
       30:  invokevirtual   #10; //Method java/io/PrintStream.println:(Ljava/lang/St
    ring;)V
       33:  goto    0
       36:  return}表現差異頗大
      

  21.   

    貌似run方法的內部細節沒有在assembly code中表現出來
      

  22.   


    嗯,我就是怕Cache在线程对象中。因为线程池似乎可以有任务队列,一个active的线程可能执行完一个任务,就直接从队列里领取一个任务继续执行了,这样的话,可能并不会清缓存。
      

  23.   

    仅就LZ的问题来说,两个锁都是非必要的。做java不能像做c++那样想,java通常情况下是解释执行的,虚拟机把你能优化的基本都优化了,不必要的锁解释执行的时候会去掉(如get_all方法的)。volatile只能保证可见性,保证不了原子性。线程池就是一个并发出口,线程的上下文线程自己带着的,和线程池没关系。
      

  24.   


    嗯,我觉得你说的“线程的上下文线程自己带着的,和线程池没关系”很有道理。线程的上下文,确实不是线程池能干预的,而是取决于jvm的行为(或者它所依赖的OS?),它认为该从内存中读,就会生成一个读内存的汇编指令,否则可能就生成一个读堆栈的汇编指令。
      

  25.   

    java5以後可以保證原子性(In Java 5 or later) Volatile reads and writes establish a happens-before relationship, much like acquiring and releasing a mutex.[8]
      

  26.   

    public class TestVolatile {
    volatile long count = 0; public static void main(String[] args) throws InterruptedException {
    final TestVolatile tv = new TestVolatile();
    for (int i = 0; i < 200; i++) {
    new Thread() {
    @Override
    public void run() {
    tv.updateCount();
    } }.start();
    }
    Thread.sleep(10000);
    System.out.println(tv.count);
    } public void updateCount() {
    for (int i = 0; i < 500; i++) {
    count = count + 1;
    }
    } public long getCount() {
    return count;
    }
    }
    volatile保证不了原子性,count<200*500.
      

  27.   


    你说的这个“原子性”和我们说的不是一个概念。32楼所说的也正是我关心的,就是在读或写(不是更新)的操作是否是原子的,注意,这仅仅是一个操作,java规范jsr133说明,volatile可以保证long和double的读或写是原子的。而你说的这个是read-update-write的操作,这必然是需要加锁的。
      

  28.   

    线程对内存的操作指令顺序read>load>use>assign>store>write,volatile保证read>load>use与assign>store>write这两组指令能看做原子的,即“读或写是原子的”,至于use到assign之间保证不了,我不知道你所说的是否是这种与“运算”无关的原子性。如果变量不发生对自身的变更运算,直接声明为final岂不更省心?
      

  29.   

    Prior to Java version 1.5, volatile did not guarantee the atomicity of a 64-bit long load or store. A second thread could view the value when only 32 bits of the new value had been stored, getting half the old value and half the new. 
    1.5之前,volatile無法保證64位的long型變量的讀或存(load or store)
    當第二個線程恰巧在只有32位的新值被存入進入時去讀的話,得到的就是一半舊值一半新值,即32位是舊的,另32位是新的,這就是讀寫不原子了
      

  30.   


    首先,我认为read和load没什么区别,store和write也是。例如a = b,转换成汇编可能就是mov  b, register
    mov  register, a既然a可以被任意赋值,自然就不能是final的。我怕的是,当数据为long时,汇编代码是这样的mov  high32 of b, register x
    mov  low32 of b, register y
    mov  register x, high32 of a
    mov  register y, low32 of a当在第三句执行完,第四句没有执行时,另一个线程读取了a,这时a就不是一个完整的了。volatile可以避免这一点。
      

  31.   

    抱歉,我不懂汇编,您的问题归结起来大概就一句话:java指令不能以原子方式执行怎么办。确实java对long和double有特殊的规范,但似乎java对这类双字节值读写异常的微概率事件也没怎么放在心上,如java.util.Date中的private transient long fastTime——java线程关注的是竞争条件和竞争数据之类问题导致的异常。java建议用volatile保证long与double的读写原子性,您也可以用AtomicLong解决问题。