使用volatile关键字与包装类

3
在一堂Java并发课程中,我被建议在多线程应用程序中使用以下代码作为计数器。
private volatile int count;

我在思考是否可以在包装类Integer中使用volatile关键字,而不是原始类型int(如下所示):

private volatile Integer count;

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

2
这是合法的,但“正确”的含义不太清楚。最好使用AtomicInteger - chrylis -cautiouslyoptimistic-
4个回答

4
实际上,这两个版本都是糟糕的设计。
来自《Java并发编程实践》第39页:
“...... volatile 的语义不足以使增量操作(count++)原子化,除非您可以保证变量仅从单个线程中写入。(原子变量确实提供原子读取-修改-写入支持,并且通常可以用作“更好的volatile变量”)”
因此,我建议使用AtomicInteger
private AtomicInteger count;

1
严格来说,这是正确的。如果一个线程设置了一个新的计数,所有其他读取它的线程都会得到新的值。
如果两个线程同时写入该值,则会遇到问题,因为不能保证在写入计数器时最后读取的值是当前值。例如,如果有两个线程和一个初始值为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个线程同时写入计数。


1
整数类是不可变的,因此当计数更改时,它会获得对新整数的引用,并且volatile关键字确保新引用在线程之间可见。
但是,如果您希望更新是原子性的,则使用AtomicInteger将是更好的选择,因为否则基于当前值进行递增将不安全。

1
标记为volatile仅在您在同步区域之外唯一要做的事情是设置或获取值时才是正确的。任何尝试进行“相对”数学运算(增加、减少等)都不是线程安全的。要执行任何此类工作,需要同步或使用AtomictInteger。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接