单线程与多线程开发中如何选择:ArrayList vs Vector

166 阅读6分钟

ArrayListVector 是 Java 中的两个列表类,它们都基于动态数组实现,并且都实现了 List 接口,因此在功能上有很多相似之处。但它们的设计侧重点不同,主要区别在于线程安全性性能问题。


1. 线程安全性

  • ArrayList

    • 不是线程安全的
    • 如果多个线程同时对 ArrayList 进行读写操作(比如添加、删除元素),可能会导致数据不一致或程序崩溃。
    • 如果需要并发安全操作,通常需要手动加锁,或者使用 Collections.synchronizedList() 方法将其转换为线程安全的版本。
  • Vector

    • 线程安全的
    • Vector 的方法(如 addremove 等)都使用了同步关键字(synchronized)进行内部加锁,确保线程安全。
    • 这意味着多个线程可以同时操作一个 Vector 而不会出现数据一致性问题。

如果需要在多线程环境下使用,Vector 是线程安全的,而 ArrayList 需要额外处理线程安全性。


2. 性能

  • ArrayList

    • 因为没有同步机制,操作效率更高。
    • 适合在单线程或不需要线程安全的场景中使用。
  • Vector

    • 因为每个方法都加了同步锁,性能会比 ArrayList 慢,即使在单线程环境下,它的同步机制也会导致不必要的性能开销。

在单线程环境中,ArrayList 的性能优于 Vector,更推荐使用 ArrayList


3. 扩容机制

  • ArrayList

    • 当数组空间不足时,按 1.5 倍的大小进行扩容(即新容量 = 当前容量 × 1.5)。
    • 这种扩容方式节省了一定的内存,但可能会导致扩容次数稍微多一些。
  • Vector

    • 当数组空间不足时,按 2 倍的大小进行扩容(即新容量 = 当前容量 × 2)。
    • 扩容倍数更大,相对减少了扩容次数,但可能会浪费更多内存。

ArrayList 的扩容机制更加节约内存,而 Vector 的扩容机制可能会浪费一些内存,但适合需要频繁扩容的场景。


4. 迭代器的使用

  • ArrayList

    • 推荐使用 Iteratorfor-each 循环来遍历。
    • 如果使用的是 Iterator,同时有其他线程修改了列表,会抛出 ConcurrentModificationException(因为 ArrayList 没有内置的线程安全机制)。
  • Vector

    • 除了可以使用 Iteratorfor-each,还支持一种过时的遍历方式:Enumeration
    • EnumerationVector 中的旧式迭代器,早于 Iterator 出现,不支持快速失败机制(即使其他线程修改了 Vector,也不会抛出异常)。

Iterator 是现代推荐的遍历方式,而 Enumeration 是过时的工具,通常不推荐使用。


5. 兼容性

  • ArrayList

    • 是 Java 1.2 中引入的,属于 Java 集合框架的一部分。
    • 更现代化,设计更符合目前 Java 的编程习惯。
  • Vector

    • 是 Java 1.0 中引入的,属于早期 Java 的类。
    • 虽然它在 Java 集合框架中得到了改造,但很多设计上显得有些“过时”,不推荐在新项目中使用。

ArrayList 是现代化的选择,而 Vector 更多是为了向后兼容早期代码。


6. 使用场景总结

  • ArrayList

    • 单线程环境。
    • 对性能要求较高。
    • 不需要线程安全。
    • 适合现代开发中绝大多数场景。
  • Vector

    • 多线程环境,但对性能要求不高。
    • 需要线程安全,但不想手动加锁。
    • 需要兼容旧代码(比如早期使用 Vector 的项目)。

7. 对比

特点ArrayListVector
线程安全性不是线程安全是线程安全
性能更快(没有同步开销)较慢(同步开销较高)
扩容机制容量不足时扩容为原来的 1.5 倍容量不足时扩容为原来的 2 倍
迭代器推荐使用 Iteratorfor-each支持 Iterator 和过时的 Enumeration
引入时间Java 1.2Java 1.0
使用场景单线程环境,性能要求高多线程环境,需要线程安全

1. ArrayList 示例

ArrayList 是非线程安全的,因此适合单线程或不需要线程安全的场景。

import java.util.ArrayList;

public class ArrayListExample {
    public static void main(String[] args) {
        // 创建一个 ArrayList
        ArrayList<String> arrayList = new ArrayList<>();

        // 添加元素
        arrayList.add("Apple");
        arrayList.add("Banana");
        arrayList.add("Cherry");

        // 遍历元素
        System.out.println("ArrayList 遍历 (for-each):");
        for (String item : arrayList) {
            System.out.println(item);
        }

        // 按索引访问元素
        System.out.println("\n访问索引 1 的元素: " + arrayList.get(1));

        // 删除元素
        arrayList.remove("Banana");
        System.out.println("\n删除 \"Banana\" 后的 ArrayList: " + arrayList);
    }
}

运行结果

ArrayList 遍历 (for-each):
Apple
Banana
Cherry

访问索引 1 的元素: Banana

删除 "Banana" 后的 ArrayList: [Apple, Cherry]

2. Vector 示例

Vector 是线程安全的,适合需要线程安全的多线程场景,但性能较低。

import java.util.Vector;

public class VectorExample {
    public static void main(String[] args) {
        // 创建一个 Vector
        Vector<String> vector = new Vector<>();

        // 添加元素
        vector.add("Dog");
        vector.add("Cat");
        vector.add("Bird");

        // 遍历元素 (支持 Enumeration)
        System.out.println("Vector 遍历 (Enumeration):");
        java.util.Enumeration<String> enumeration = vector.elements();
        while (enumeration.hasMoreElements()) {
            System.out.println(enumeration.nextElement());
        }

        // 按索引访问元素
        System.out.println("\n访问索引 2 的元素: " + vector.get(2));

        // 删除元素
        vector.remove("Cat");
        System.out.println("\n删除 \"Cat\" 后的 Vector: " + vector);
    }
}

运行结果

Vector 遍历 (Enumeration):
Dog
Cat
Bird

访问索引 2 的元素: Bird

删除 "Cat" 后的 Vector: [Dog, Bird]

3. ArrayList 在多线程中的问题

ArrayList 是非线程安全的,在多线程场景下直接使用可能导致数据不一致或抛出异常。

import java.util.ArrayList;

public class ArrayListMultiThreadIssue {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();

        // 创建线程1:向列表中添加元素
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                list.add(i);
            }
        });

        // 创建线程2:遍历列表元素
        Thread t2 = new Thread(() -> {
            for (Integer num : list) {
                System.out.println(num);
            }
        });

        t1.start();
        t2.start();
    }
}

可能出现的问题

  • ArrayList 的内部结构在多个线程中同时被修改,可能导致崩溃或抛出 ConcurrentModificationException

4. Vector 在多线程中的表现

由于 Vector 的方法是同步的,它可以在多线程环境中安全使用。

import java.util.Vector;

public class VectorMultiThreadSafe {
    public static void main(String[] args) {
        Vector<Integer> vector = new Vector<>();

        // 创建线程1:向 Vector 中添加元素
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                vector.add(i);
            }
        });

        // 创建线程2:遍历 Vector 中的元素
        Thread t2 = new Thread(() -> {
            for (Integer num : vector) {
                System.out.println(num);
            }
        });

        t1.start();
        t2.start();
    }
}

运行结果
Vector 在多线程环境下不会抛出异常或导致数据不一致,因为它的操作是线程安全的。


5. 手动同步 ArrayList

如果您希望在多线程中使用 ArrayList,可以通过显式加锁实现线程安全。

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

public class SynchronizedArrayListExample {
    public static void main(String[] args) {
        // 使用 Collections.synchronizedList 方法将 ArrayList 转为线程安全
        List<Integer> synchronizedList = Collections.synchronizedList(new ArrayList<>());

        // 创建线程1:向列表中添加元素
        Thread t1 = new Thread(() -> {
            synchronized (synchronizedList) { // 手动同步块
                for (int i = 0; i < 1000; i++) {
                    synchronizedList.add(i);
                }
            }
        });

        // 创建线程2:遍历列表中的元素
        Thread t2 = new Thread(() -> {
            synchronized (synchronizedList) { // 手动同步块
                for (Integer num : synchronizedList) {
                    System.out.println(num);
                }
            }
        });

        t1.start();
        t2.start();
    }
}

注意事项

  • 使用 Collections.synchronizedList 可以让列表本身线程安全,但在遍历时仍需要手动加锁以避免并发问题。

总结

  1. 如果您在开发中需要一个动态数组,并且不涉及多线程,优先选择 ArrayList
  2. 如果您需要线程安全的列表,又不想手动加锁,可以考虑 Vector,但更推荐使用 CopyOnWriteArrayList(性能更优的线程安全列表)。
  3. Vector 是一个过时的类,更多是为了向后兼容,建议在现代开发中避免使用。