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