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

时间:2019-06-09 02:30:19

标签: 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 not modified anywhere in the class
        this.currentIndex = new AtomicInteger(0);
    }

    public String select() {
        return list.get(currentIndex.getAndIncrement() % list.size());
    }
}

现在我的问题:

仅使用AtomicInteger才可以使此类真正具有线程安全性,还是必须有其他线程安全机制来确保线程安全(例如锁)?

2 个答案:

答案 0 :(得分:3)

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

即使在不安全地发布对currentIndex对象的引用的情况下,也必须将listfinal字段设为MyClass,以实现完全线程安全。

private final AtomicInteger currentIndex;
private final List<String> list;

实际上,如果您始终确保MyClass对象本身已安全发布,例如,如果在主线程上创建它,则在使用该对象的任何线程启动之前,都不会需要将字段设置为final

安全发布意味着对MyClass对象本身的引用是通过保证在the Java Memory Model中具有多线程顺序的方式完成的。

可能是:

  • 所有使用该引用的线程都是在启动该线程之前从启动它们的线程初始化的字段中获取的
  • 所有使用引用的线程都从与设置引用的代码在同一对象上同步的方法获取它(您的字段具有同步的getter和setter)
  • 您创建包含引用volatile
  • 的字段
  • 如果按照section 17.5 of the JLS.中的描述初始化了最后一个字段,则它位于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。)