在Java Concurrency的类中,我建议在多线程应用程序中使用以下代码作为计数器
private volatile int count;
我问自己是否可以将volatile关键字与包装类Integer一起使用而不是原始类型int(见下文):
private volatile Integer count;
在这种情况下使用Integer包装器类是否正确?
答案 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个线程写入计数。