我有一个Java类,这是它的代码:
public class MyClass {
private AtomicInteger currentIndex;
private List<String> list;
MyClass(List<String> list) {
this.list = list; // list is initialized only one time in this constructor and is not modified anywhere in the class
this.currentIndex = new AtomicInteger(0);
}
public String select() {
return list.get(currentIndex.getAndIncrement() % list.size());
}
}
现在我的问题:
仅使用AtomicInteger
才可以使此类真正具有线程安全性,还是必须有其他线程安全机制来确保线程安全(例如锁)?
答案 0 :(得分:3)
使用currentIndex.getAndIncrement()
是完全线程安全的。但是,您需要更改代码以使其在所有情况下均是线程安全的。
即使在不安全地发布对currentIndex
对象的引用的情况下,也必须将list
和final
字段设为MyClass
,以实现完全线程安全。
private final AtomicInteger currentIndex;
private final List<String> list;
实际上,如果您始终确保MyClass
对象本身已安全发布,例如,如果在主线程上创建它,则在使用该对象的任何线程启动之前,都不会需要将字段设置为final
。
安全发布意味着对MyClass
对象本身的引用是通过保证在the Java Memory Model中具有多线程顺序的方式完成的。
可能是:
volatile
final
字段中。答案 1 :(得分:3)
我认为您的代码包含两个错误。
首先,通常,当您像构造函数一样从某个未知来源接收到对象时,请进行防御性复制,以确保未在类外部对其进行修改。
MyClass(List<String> list) {
this.list = new ArrayList<String>( list );
因此,如果执行此操作,您现在是否需要在班级中的任何地方对该列表进行变异?如果是这样,则方法:
public String select() {
return list.get(currentIndex.getAndIncrement() % list.size());
不是原子的。这里可能发生的是线程调用getAndIncrement()
,然后执行模数(%
)。然后,如果此时它已与另一个从列表中删除项目的线程换出,则list.size()
的旧限制将不再有效。
我认为除了将synchronized
添加到整个方法之外,没有其他用途:
public synchronized String select() {
return list.get(currentIndex.getAndIncrement() % list.size());
与其他任何增幅器相同。
(在实例字段上仍然需要其他张贴者提到的{final
。)