Java中使用原子变量的线程安全

Java中使用原子变量的线程安全,java,multithreading,concurrency,java-8,thread-safety,Java,Multithreading,Concurrency,Java 8,Thread Safety,我有一个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 n

我有一个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());
    }
}
公共类MyClass{
私有原子索引;
私人名单;
MyClass(列表){
this.list=list;//list在此构造函数中仅初始化一次,并且不会在类中的任何位置进行修改
this.currentIndex=新的原子整数(0);
}
公共字符串选择(){
返回list.get(currentIndex.getAndIncrement()%list.size());
}
}
现在我的问题是:


由于只使用了
AtomicInteger
,这个类真的是线程安全的吗?或者必须有额外的线程安全机制来确保线程安全(例如锁)?

使用
currentIndex.getAndIncrement()
是完全线程安全的。但是,您需要对代码进行更改,以使其在所有情况下都是线程安全的

需要将字段
currentIndex
list
设置为
final
,以实现全线程安全,即使是在不安全地发布对
MyClass
对象的引用时也是如此

private final AtomicInteger currentIndex;
private final List<String> list;
私有最终原子整数currentIndex;
私人最终名单;
实际上,如果始终确保安全发布
MyClass
对象本身,例如,如果在启动使用它的任何线程之前在主线程上创建它,则不需要将字段设置为
final

安全发布意味着对
MyClass
对象本身的引用是以一种保证多线程排序的方式进行的

可能是:

  • 所有使用该引用的线程都从一个字段中获取该引用,该字段在其线程启动之前由启动它们的线程初始化
  • 所有使用引用的线程都从与设置引用的代码在同一对象上同步的方法获取引用(该字段有一个同步的getter和setter)
  • 将包含引用的字段设置为volatile
  • 如果按照中所述初始化了最后一个字段,则它位于最后一个字段中
  • 还有一些案例不容易用于发布引用

    • 我认为您的代码包含两个bug

      首先,通常当您像构造函数一样从未知源接收对象时,您会制作一个防御副本,以确保它不会在类之外被修改

      MyClass(List<String> list) {
          this.list = new ArrayList<String>( list ); 
      
      它不是原子的。这里可能发生的是线程调用
      getAndIncrement()
      ,然后执行模数(
      %
      )。然后,如果它与另一个从列表中删除项目的线程交换,则旧的
      list.size()
      限制将不再有效

      我认为除了在整个方法中添加
      synchronized
      之外,没有别的方法:

      public synchronized String select() {
          return list.get(currentIndex.getAndIncrement() % list.size());
      
      和其他任何变异子一样


      final
      如其他海报所述,实例字段上仍然需要。)

      感谢您的回答。但是我不明白你所说的“即使不安全地发布对MyClass对象的引用”是什么意思。你能详细说明一下吗?我在OP中读到了这条评论“列表在这个构造函数中只初始化了一次,并且在类中的任何地方都没有修改”,因为列表在构造函数中赋值后没有修改。啊,错过了。但现在我有另一个问题。
      public synchronized String select() {
          return list.get(currentIndex.getAndIncrement() % list.size());