如果字段的字段不可变,外部同步的ArrayList线程是否安全?

问题描述 投票:2回答:2

让我们假设这个:

  1. 有一个ArrayList
  2. 列表被多个线程访问。线程可以添加元素并遍历所有元素。
  3. 所有访问都在外部同步。因此,不可能两个线程同时访问列表。

查看ArrayList源代码,我们可以看到size和elementData字段不是可变的:

    transient Object[] elementData; // non-private to simplify nested class access

    private int size;

另外,让我们看一下add方法:

    /**
     * This helper method split out from add(E) to keep method
     * bytecode size under 35 (the -XX:MaxInlineSize default value),
     * which helps when add(E) is called in a C1-compiled loop.
     */
    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }

会发生类似的事情吗?

  1. 假设列表包含4个元素。
  2. 线程A添加了新元素。大小已更新为5。
  3. 线程B添加了新元素。缓存了大小,线程B看到了它的旧值(4)。因此,不添加新元素,而是覆盖最后一个元素。

elementData是否会发生类似情况?

java multithreading arraylist volatile
2个回答
1
投票

TL; DR:通过正确的同步,您描述的问题是不可能的。


Java具有相当复杂的内存模型。 JVM可以自由地在内部对语句进行重新排序,以便更有效地执行它们,但要遵守这样的约束条件:您必须知道该约束可以告诉线程内的操作已重新排序。

[本质上,这就像一个老板说:“我不在乎您如何完成工作,只需要在[一段时间]内完成[工作]。]]

困难之处在于,尽管它说您一定不能看到线程内的重新排序,但它并不意味着不同的线程不能看到彼此以不同的顺序进行工作。

这是令人头晕的东西。简化的概念是happens-before

的思想。您可以在两个线程中做某些事情,以确保一个线程完成的事情在另一线程尝试使用它们的结果时似乎已经发生。从字面上看,一个线程中的事物已“发生在”另一个线程中的事物“之前”。

有许多众所周知的事物会在关系发生之前发生:

  • 写易失性变量发生在前读取同一变量
  • 退出同步块之前发生
  • 使用同一监视器进入同步块。换句话说,由一个线程在同步块内发生的写入对于其他执行相同代码的线程而言是可见的。

    因此,易失性和同步都是创建事件的两种方法,这是保证一个线程完成的操作(某些事情)被另一线程看到的必要条件。>>

    但是两者之间有区别:

  • Volatile为您提供可见性:它确保可以看到写入内容
  • 同步为您提供可见性和原子性:它确保写入是可见的,但它还可以确保其他人没有同时做某事。
  • 在添加到ArrayList的情况下,需要原子性,因为您要做的不只是一件事情:增加大小和分配新的数组元素。

也使变量易失性在正确性方面毫无用处,但在模态情况下会使代码变慢,在模态情况下,ArrayList只能从单个线程访问。

因此,只要您的代码正确

同步-也就是说,对列表的所有访问都在同一件事上同步,例如在列表本身上-由于原子性和可见性,您描述的情况不会发生同步属性。

我先前对您的问题有误解,又添加了另一个答案。抱歉。

关于ArrayList中size字段的波动性,您的担心是合理的。您描述的错误是可能的。这是an article describing exactly the same issue

volatile引用对象的情况下,请确保该引用本身将及时对其他线程可见,但对于其成员变量而言,情况并非如此。如果单独访问,不能保证对象中包含的数据将始终可见。

对于java.util中的标准ArrayList,您无能为力,但是您可以通过使自己的类与ArrayList紧密镜像,但正确设置实例变量的可变性,来轻松解决此问题。这也可以为您提供一种更简单(或更易于阅读)的方式来同步列表,而不是在外部进行同步。


-1
投票

我先前对您的问题有误解,又添加了另一个答案。抱歉。

© www.soinside.com 2019 - 2024. All rights reserved.