首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > JAVA > J2SE开发 >

java多线程更新时的有关问题,请大牛解释

2012-05-04 
java多线程更新时的问题,请大牛解释。一个类暴露出的借口只有一个Object(int[] 可以看做是一个Object),我当

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


(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]

引用類型可能是由於引用只需一步操作,所以原子
[解决办法]

探讨

谢谢1楼,请问哪里有比较官方的介绍吗?是否是说values = new_values;实际已经改变了values所指向的内存地址,而不是将new_values里面的东西赋值给values了?

我看AtomicLong里面的实现,用了volatile long。

可是volatile能够保证原子性吗?我只知道volatile可以避免cpu cache引起的错误,能够保证每次都操作内存。……

[解决办法]
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實現的內部原子化操作
[解决办法]
探讨

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

[解决办法]
探讨

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

[解决办法]
探讨

回12楼,你说的这个例子我前两天刚试过。也明白volatile的重要性了。另外发现一个特性就是,在循环内打印这个变量,有时候可以退出这个循环。我不太清楚是否jvm觉得打印很耗时间,就没必要cache了。

其实我的任务环境是“请求-回复”的线程池应用,一个线程并不会做这样大量的密集运算,可能执行少量操作就释放了这个线程。

后来我发现既然不频繁读取volatile变量,那也就无所谓效率低……

[解决办法]
探讨

回12楼,你说的这个例子我前两天刚试过。也明白volatile的重要性了。另外发现一个特性就是,在循环内打印这个变量,有时候可以退出这个循环。我不太清楚是否jvm觉得打印很耗时间,就没必要cache了。

其实我的任务环境是“请求-回复”的线程池应用,一个线程并不会做这样大量的密集运算,可能执行少量操作就释放了这个线程。

后来我发现既然不频繁读取volatile变量,那也就无所谓效率低……

[解决办法]
探讨

回12楼,你说的这个例子我前两天刚试过。也明白volatile的重要性了。另外发现一个特性就是,在循环内打印这个变量,有时候可以退出这个循环。我不太清楚是否jvm觉得打印很耗时间,就没必要cache了。

其实我的任务环境是“请求-回复”的线程池应用,一个线程并不会做这样大量的密集运算,可能执行少量操作就释放了这个线程。

后来我发现既然不频繁读取volatile变量,那也就无所谓效率低……

[解决办法]
再驗證一下,看一下純的while循環
Java code
public class Test {    private  static boolean stop;        public static void main(String[] args) throws Exception {                while(!stop) {            System.out.println("stop=" + stop);        }                   }} 


[解决办法]

探讨

引用:

cache在哪里?线程对象么?new ThreadPool的时候带了ThreadFactory参数?

如果是这样的话,就有看线程池的策略了

Executors类大部分new出来的线程池都是ThreadPoolE……


嗯,我就是怕Cache在线程对象中。因为线程池似乎可以有任务队列,一个active的线程可能执行完一个任务,就直接从队列里领取……

[解决办法]
探讨

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

[解决办法]
探讨
我怕的是,当数据为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可以避免这一点。

热点排行