Java 多个线程操作同一个 List 的安全性问题与解决方案

在 Java 编程中,多线程编程是一个重要而复杂的主题。尤其是,当多个线程同时操作同一个集合(如 List)时,可能会导致数据的不一致和程序崩溃。本文将探讨在多线程环境中,如何安全地操作同一个 List,并提供相应的代码示例。

问题背景

在 Java 中,ArrayListLinkedList 等类并不是线程安全的。当多个线程同时访问同一个 List 实例,而至少有一个线程修改了列表时,会导致不一致的状态。例如,一个线程正在迭代列表,另一个线程可能会在此期间添加或删除元素,这可能会引发 ConcurrentModificationException

类图

下面是涉及的类图,展示了 ThreadSafeListMain 类之间的关系。

classDiagram
    class ThreadSafeList {
        +void add(String item)
        +List<String> getList()
    }
    
    class Main {
        +void main(String[] args)
    }
    
    Main --> ThreadSafeList

解决方案

为了确保线程安全,可以考虑使用 Java 提供的多种方式,比如同步(synchronized)、使用 Collections.synchronizedList 或者使用 CopyOnWriteArrayList

使用 synchronized 关键字

我们可以通过同步方法或同步块来确保同一时间只有一个线程能够访问被保护的代码。以下是一个使用同步方法的示例:

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

class ThreadSafeList {
    private final List<String> list;

    public ThreadSafeList() {
        this.list = new ArrayList<>();
    }

    public synchronized void add(String item) {
        list.add(item);
    }

    public synchronized List<String> getList() {
        return new ArrayList<>(list);
    }
}

使用 Collections.synchronizedList

另一种方法是使用 Collections.synchronizedList,这个方法为 List 提供了一个线程安全的封装。但在迭代的时候,仍然需要手动进行同步。

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

class ThreadSafeList {
    private final List<String> list;

    public ThreadSafeList() {
        this.list = Collections.synchronizedList(new ArrayList<>());
    }

    public void add(String item) {
        list.add(item);
    }

    public List<String> getList() {
        synchronized (list) {
            return new ArrayList<>(list);
        }
    }
}

使用 CopyOnWriteArrayList

Java 还提供了 CopyOnWriteArrayList,这是一个对写操作使用复制的线程安全反应式集合。适用于读多写少的场景。

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

class ThreadSafeList {
    private final List<String> list;

    public ThreadSafeList() {
        this.list = new CopyOnWriteArrayList<>();
    }

    public void add(String item) {
        list.add(item);
    }

    public List<String> getList() {
        return new ArrayList<>(list);
    }
}

使用示例

以下是一个在多线程环境中使用 ThreadSafeList 的示例:

public class Main {
    public static void main(String[] args) {
        ThreadSafeList threadSafeList = new ThreadSafeList();

        Runnable addTask = () -> {
            for (int i = 0; i < 5; i++) {
                threadSafeList.add(Thread.currentThread().getName() + " - Item " + i);
                System.out.println(Thread.currentThread().getName() + " added Item " + i);
            }
        };

        Thread thread1 = new Thread(addTask, "Thread-1");
        Thread thread2 = new Thread(addTask, "Thread-2");

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final list: " + threadSafeList.getList());
    }
}

序列图

下面的序列图展示了两个线程如何同时访问 ThreadSafeList

sequenceDiagram
    participant Thread1
    participant Thread2
    participant ThreadSafeList

    Thread1->>ThreadSafeList: add("Thread-1 - Item 0")
    Thread2->>ThreadSafeList: add("Thread-2 - Item 0")
    ThreadSafeList-->>Thread1: Success
    ThreadSafeList-->>Thread2: Success
    Thread1->>ThreadSafeList: add("Thread-1 - Item 1")
    Thread2->>ThreadSafeList: add("Thread-2 - Item 1")

结论

在 Java 中处理多线程时,确保数据的线程安全至关重要。通过上述的不同方法,我们可以安全地在多线程环境中对 List 进行操作。理解不同的方法和它们的适用场景,可以帮助我们根据需要选择最合适的解决方案。在后续的项目中,开发者应始终关注由于并发所带来的潜在安全问题,以确保应用的稳定性和可靠性。