当前位置: 首页 > 知识库问答 >
问题:

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

锺高翰
2023-03-14

让我们假设这样:

  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添加新元素。Size被缓存,线程B看到它的旧值(4)。因此,不是添加新元素,而是覆盖最后一个元素。

ElementData是否也会出现类似的情况?

共有1个答案

房学文
2023-03-14

TL;DR:在正确的同步下,您描述的问题是不可能的,因为同步确保了操作的原子性和可见性。

JVM执行Java代码的方式相当复杂。可以自由地对与Java代码中的表达式和语句相对应的指令进行重新排序,以便更高效地执行它们,前提是您不能分辨出某个线程对其操作进行了重新排序。

从本质上说,这就像一个老板说“我不在乎你如何完成工作,只要在某个时候完成工作就行了”。

它的困难在于,虽然它说你不能看到一个线程内的重新排序,但它并没有说不同的线程不能看到彼此以不同的顺序做事情。

这是令人头晕目眩的东西。简化的概念是发生之前的想法。您可以在两个线程中执行某些操作,以确保在另一个线程尝试使用它们的结果时,一个线程执行的操作似乎已经发生。从字面上看,一个线程中的事情“发生在”另一个线程中的事情之前。(继续做工作类比,这就像你必须把你已经完成的工作交给一个同事,让他们来做他们的工作:他们可以接受你已经完成的工作,去做他们的工作,而不管你是如何完成的)。

有许多众所周知的事情创造了发生之前的关系。与本问题有关的是:

  • 对volatile变量的写入发生在对同一变量的读取之前。这通常被描述为“数据总是写入主内存和从主内存读取,而不是由线程缓存”。
  • 退出同步块时,将在进入具有相同监视器的同步块之前进行。换句话说,由一个线程在同步块内进行的写入对执行在同一件事中同步的代码的其他线程是可见的

因此,volatile和synchronized都是创建happens的两种方法--在此之前,这是为了保证一个线程所做的事情被另一个线程看到。

但两者是有区别的:

  • volatile为您提供了可见性:它确保写入是可见的。
  • 同步为您提供了可见性和原子性:它确保写入是可见的,但它还确保只要特定监视器被保留,就不会有其他人同时执行某些操作。

在添加到arraylist的情况下,需要原子性,因为您要做的不止一件事:增加大小并分配新的数组元素。

使变量也变得易变,在正确性方面没有任何作用,但在模态情况下会使代码变慢,在模态情况下,arraylist只能从单个线程访问。

因此,如果您的代码是正确同步的,也就是说,对列表的所有访问都在同一件事情上同步,例如在列表本身上同步,那么您描述的情况就不会发生,因为同步的原子性和可见性属性。

 类似资料:
  • 我尝试使用ArrayList解决生产者和消费者问题(我知道ArrayList是nt threadsafe),我确保使用关键字放置列表,但仍然进入。这就是错误 启动生产者请提供作业详细信息:TestJob作业完成:TestJob Exception位于java.util.ArrayList$itr.checkforcoModification(未知源)位于test.thread.consumer.r

  • 考虑以下代码: 在一次采访中,我被问到了以下问题:如果只有一个线程调用,而多个线程可以调用,那么这个代码是线程安全的吗? 我知道,如果我们有多个写入线程,竞争条件可能会发生。然而,当每个读取线程中只有一个写入线程时,我们将按顺序获取值,即我们不会在之后读取,因为的写入发生在第一次读取,因此,在同一线程中的所有后续读取。这个推理是正确的,还是我漏掉了什么?

  • 最近我在读一些关于java并发的书。关于线程安全,如果不可能使一个类变为inmutable,那么可以通过同步它的数据来确保线程安全。 下面的类显然不是线程安全的 然后我可以同步写,但它不会保持线程安全 因为我不仅需要同步写入,还需要同步读取 现在的问题是,通过使用易失性,我可以保证其他线程会看到更新的值,所以这让我认为这个类应该是线程安全的 最后一个类线程安全吗??

  • 问题内容: 我在想,如果您有一个静态方法 不 同步,但并 没有 修改任何静态变量是线程安全的?如果该方法在其中创建局部变量该怎么办?例如,以下代码是线程安全的吗? 因此,如果我有两个线程连续且同时调用此方法,一个带狗(例如“大丹狗”和“斗牛犬”),另一个带猫(例如“波斯”和“暹罗”)的猫,我将得到猫和狗在同一阵列中?还是猫和狗永远不会同时处于该方法的同一调用中? 问题答案: 此方法是100%线程安

  • 如果我决定使用一个非线程安全的集合并同步它的访问,我是否需要同步构造函数中的任何突变?例如,在下面的代码中,我知道对列表的引用在构造后对所有线程都是可见的,因为它是最终的。但我不知道这是否构成安全发布,因为构造函数中的add没有同步,而且它正在ArrayList的elementData数组中添加一个引用,这是非最终的。

  • 我要创建一个程序,给定N个线程,这些线程可以在队列中插入或删除一个元素,但是线程访问队列是有条件的: null 我用同步块做的,就像这样: run void很简单,它只是在插入或删除元素时永远循环。 我的问题是,在不使用synchronized的情况下,我如何遵循那个条件? 没有同步块,怎么可能保证互斥呢? 编辑:我不能使用类似于同步的东西(就像锁一样)