将volatile关键字与包装类一起使用

时间:2014-06-19 14:55:11

标签: java multithreading concurrency

在Java Concurrency的类中,我建议在多线程应用程序中使用以下代码作为计数器

private volatile int count;

我问自己是否可以将volatile关键字与包装类Integer一起使用而不是原始类型int(见下文):

private volatile Integer count;

在这种情况下使用Integer包装器类是否正确?

4 个答案:

答案 0 :(得分:5)

实际上两个版本都是糟糕的设计。

来自Java Concurrency in Practice p。 39:

  

... volatile的语义不足以使递增操作(count ++)成为原子,除非你能保证变量只是从一个线程写入。 (原子变量确实提供原子读 - 修改 - 写支持,通常可以用作“更好的易变量”)

所以我建议使用AtomicInteger

private AtomicInteger count;

答案 1 :(得分:2)

Integer类是不可变的,因此当计数更改时,它会获得对新Integer的引用,而volatile关键字确保新引用在线程中可见。

但是如果你希望更新是原子的,那么使用AtomicInteger会是一个更好的选择,因为基于当前值的增加将是不安全的。

答案 2 :(得分:2)

如果您在同步区域之外做的唯一事情是设置或获取值,则标记为易失性只是正确的。任何尝试“相对”数学(递增,递减等)都不是线程安全的。要完成任何此类工作,需要同步或使用AtomictInteger。

答案 3 :(得分:2)

严格来说,这是正确的。如果一个线程设置了一个新计数,则读取它的每个其他线程都将获得新值。

如果两个线程同时写入值,则会遇到问题,因为从来没有保证您上次为计数器读取的值是写入计数器时的值。例如,如果您有两个线程,并且计数器从0开始。

Thread 1: int temp = count.intValue(); //temp = 0;
Thread 2: int temp = count.intValue(); //temp = 0;
Thread 1: count = new Integer(temp+1); //count = 1;
Thread 2: count = new Integer(temp+1); //count = 1;

如您所见,您将计数器递增两次但值仅增加1.即使您将命令更改为

,也会发生相同的行为
count =  new Integer(count.intValue() + 1);

由于JVM仍然需要读入值,递增并写出来,每个都至少有1个周期。

要避免这种情况,请使用AtomicInteger(不需要使用volatile),如@chrylis所建议的那样,或者使用同步和/或锁定来确保永远不会有2个线程写入计数。