日期:2014-05-20  浏览次数:20726 次

java多线程更新时的问题,请大牛解释。
一个类暴露出的借口只有一个Object(int[] 可以看做是一个Object),我当时是用写加锁,读不加锁的方式。
Java code

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的赋值,可能不是原子操作,所以改为了
Java code

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.基本类型,引用类型的赋值,引用是原子的操作;
2.long与double的赋值,引用是可以分割的,非原子操作;
3.要在线程间共享long或double的字段时,必须在synchronized中操作,或是声明成volatile.

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

所以,你这种赋值,不需要锁。

------解决方案--------------------
请问哪里有比较官方的介绍吗?
——看看Thinking In Java吧,这书确实不错。

否是说values = new_values;实际已经改变了values所指向的内存地址,而不是将new_values里面的东西赋值给values了?
——这是必然的,你所期望的赋值,只有原始类型才有,对象全都是引用。

我看AtomicLong里面的实现,用了volatile long。
——这是因为long和double,超出32位,也就是高位和低位的赋值会分为两条语句执行;所任默认情况下就无法实现原子性了

可是volatile能够保证原子性吗?我只知道volatile可以避免cpu cache引起的错误,能够保证每次都操作内存。
——两种能力都提供了,或者说因为volatile要提供可见性,所以也就必须保证其原子性
------解决方案--------------------
读的时候也是要加锁的

不管赋值是不是原子,values是非volatile的,当a线程给其赋值后,b线程未必可见,也就说b线程看到的可能还是老的值
------解决方案--------------------
并发度和取舍问题。如果并发频密,读取到老数据的概率就会很高。

其实volatile的开销相比synchronized,低很多的。
------解决方案--------------------
如果只是你提供的代码,只需要把values 定义成volatile即可,get和update都不需要同步锁。不管是32位还是64位jvm,都必需保证volatile变量的取值和赋值是原子操作。
------解决方案--------------------
http://en.wikipedia.org/wiki/Volatile_variable

The 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.)