如何在 Java 中同步 ArrayList

ArrayList 是 Java 中一个非常有用的 Collection,我想也是最常用的一个,但它不是同步的。 这是什么意思? 这意味着我们不能在多个线程之间共享 ArrayList 的实例,如果它们不仅从中读取而且还写入或更新元素。 那么如何 同步ArrayList 呢? 好吧,我们稍后会讲到这个,但是我们是否首先想到为什么 ArrayList 不同步? 既然多线程是Java的核心优势,几乎所有的Java程序都不止一个线程,为什么Java设计者不让 ArrayList 在这样的环境下使用更方便呢?

答案在于性能,与同步相关的性能成本,并且使 ArrayList 同步会使它变慢。 所以,他们肯定考虑过它并让 ArrayList 保持非同步以保持快速,但与此同时他们提供了使其同步的简单方法,这就是我们将在本篇文章中学习的内容。

JDK Collections 类有几种方法来创建同步的 ListSet 和 Map,我们将使用 Collections.synchronizedList() 方法使我们的 ArrayList 同步。 此方法接受一个 List,它可以是 List 接口的任何实现,例如 ArrayListLinkedList,并返回由指定列表支持的同步(线程安全)列表。 所以你也可以使用这个技巧在Java中让 LinkedList 同步并且线程安全。


同步列表和迭代

在多个线程之间共享 ArrayList 的主要挑战之一是如何处理一个线程试图访问被其他线程删除的元素的情况。 如果我们使用 get(index) 或 remove(index) 方法来检索或删除元素,那么其他线程也可能正在删除其他元素。

这意味着如果不首先检查列表的大小,我们将无法可靠地调用 get(index) 或 remove(index),然后我们还需要在对 size() 和 remove(int index) 的调用之间提供额外的同步。

为了保证串行访问,对支持列表的所有访问都通过返回列表完成,这一点至关重要,用户在迭代返回列表时必须手动同步返回列表,如以下示例代码所示:

 List list = Collections.synchronizedList(new ArrayList());
      ...
  synchronized(list) {
      Iterator i = list.iterator(); // Must be in synchronized block
      while (i.hasNext())
          foo(i.next());
 }

正如 Java 文档中所建议的,不遵循此建议可能会导致不确定的行为。 如果提供的 ArrayList 是可序列化的,则该列表也将是可序列化的。


用于同步 ArrayList 的 Java 程序

这是在 Java 中同步 ArrayList 的完整示例。 如果大家看的话,我们已经创建了一个字符串列表并向其中添加了几个元素。 然后我们将这个 ArrayList 传递给 Collections.synchronizedList() 方法,它返回一个线程安全的同步版本的支持列表。

现在可以在多个线程之间安全地共享此列表,但在从 ArrayList 中检索或删除元素时需要小心一点。 为了安全访问,我们应该使用 Iterator 从列表中获取和删除元素,并且以同步方式进行,如本例所示。

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

/**
 * 如何在 Java 中同步 ArrayList
 */
public class SynchronizedArrayListDemo {
    public static void main(String args[]) {
        // 一个不同步的 ArrayList List<String>
        listOfSymbols = new ArrayList<String>();
        listOfSymbols.add("RELIANCE");
        listOfSymbols.add("TATA");
        listOfSymbols.add("TECHMAH");
        listOfSymbols.add("HDFC");
        listOfSymbols.add("ICICI");
        // 同步 ArrayList
        listOfSymbols = Collections.synchronizedList(listOfSymbols);

        // 在迭代同步列表时,我们必须对其进行同步以避免非确定性行为
        synchronized (listOfSymbols) {
            Iterator<String> myIterator = listOfSymbols.iterator();
            while (myIterator.hasNext()) {
                System.out.println(myIterator.next());
            }
        }
    }
}

这就是关于如何在 Java 中同步 ArrayList 的全部内容。 如大家所知,由于 Collections.synchronizedList() 方法,在 Java 中执行此操作非常容易,但是在从 List 中检索和删除对象时需要更加小心。

顺便说一句,我们可以使用 Vector 和 CopyOnWriteArrayList 形式的更多选项。

Vector 是一个非常古老的类,但在 Java 1.4 中被改造以实现 List 接口,更重要的是它是同步的,因此我们不需要再次同步它。 顺便说一句,在使用 Vector 时,请确保我们通过 List 接口使用它,而不是使用遗留方法,否则以后将无法替换实现。

另一方面,CopyOnWriteArrayList 是 Java 中并发集合类的一部分,并且比 Vector 和 ArrayList 更具可扩展性。 如果我们的程序主要从 List 读取并偶尔更新它,那么这可能是正确的选择。