ZetCode

Java Collections.synchronizedList 方法

上次修改时间:2025 年 4 月 20 日

Collections.synchronizedList 方法提供对 List 实现的线程安全访问。 它返回由指定的列表支持的同步(线程安全)列表。 所有操作都是同步的,防止并发修改问题。

此方法是 Java 集合框架实用程序类的一部分。 它包装现有的 List 实现以使其线程安全。 返回的列表必须手动同步以进行迭代。

Collections.synchronizedList 概述

synchronizedList 方法创建一个围绕 List 的线程安全包装器。 它同步所有单独的操作,如添加、删除和获取。 这可以防止在多线程环境中出现数据损坏。

虽然单个操作是线程安全的,但复合操作需要外部同步。 迭代也需要手动同步以防止 ConcurrentModificationException。 该方法在 Collections 实用程序类中声明。

基本的 synchronizedList 示例

此示例演示了如何从 ArrayList 创建一个同步列表。 我们执行基本操作以显示线程安全的访问。 该示例展示了如何创建和使用同步列表。

BasicSynchronizedList.java
package com.zetcode;

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

public class BasicSynchronizedList {

    public static void main(String[] args) {
        
        // Create regular ArrayList
        List<String> normalList = new ArrayList<>();
        
        // Create synchronized list
        List<String> syncList = Collections.synchronizedList(normalList);
        
        // Add elements - thread-safe
        syncList.add("Apple");
        syncList.add("Banana");
        syncList.add("Cherry");
        
        // Remove element - thread-safe
        syncList.remove("Banana");
        
        System.out.println("Synchronized list: " + syncList);
    }
}

此代码显示了 Collections.synchronizedList 的基本用法。 我们创建一个常规的 ArrayList,然后将其包装在同步列表中。 对 syncList 的所有操作都是线程安全的。

输出结果表明同步列表的行为就像一个普通列表,但具有线程安全性。 像 add 和 remove 这样的单个操作是原子的。

多线程访问示例

此示例演示了从多个线程对同步列表进行线程安全的访问。 我们创建了多个并发修改列表的线程。 同步包装器可防止数据损坏。

MultiThreadSynchronizedList.java
package com.zetcode;

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

public class MultiThreadSynchronizedList {

    public static void main(String[] args) throws InterruptedException {
        
        List<Integer> numbers = Collections.synchronizedList(new ArrayList<>());
        
        // Create and start multiple threads
        Thread[] threads = new Thread[5];
        for (int i = 0; i < threads.length; i++) {
            final int threadId = i;
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    numbers.add(threadId * 100 + j);
                }
            });
            threads[i].start();
        }
        
        // Wait for all threads to complete
        for (Thread t : threads) {
            t.join();
        }
        
        System.out.println("Total elements: " + numbers.size());
    }
}

此示例展示了多个线程如何安全地访问同步列表。 每个线程向共享列表中添加 100 个元素。 如果没有同步,这将导致数据损坏。

输出结果显示了 500 个元素的正确总数(5 个线程 × 每个线程 100 个元素)。 同步列表确保在并发修改期间进行线程安全的访问。

迭代同步列表

迭代同步列表需要手动同步。 虽然单个操作是线程安全的,但迭代涉及多个操作。 此示例展示了正确的迭代方式。

SynchronizedListIteration.java
package com.zetcode;

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

public class SynchronizedListIteration {

    public static void main(String[] args) {
        
        List<String> syncList = Collections.synchronizedList(new ArrayList<>());
        
        syncList.add("Red");
        syncList.add("Green");
        syncList.add("Blue");
        
        // Proper synchronized iteration
        synchronized(syncList) {
            for (String color : syncList) {
                System.out.println(color);
            }
        }
        
        // Unsafe iteration (may throw ConcurrentModificationException)
        // for (String color : syncList) {
        //     System.out.println(color);
        // }
    }
}

此示例演示了迭代同步列表的正确方法。 整个迭代块被包装在一个同步块中,使用列表作为锁。

注释掉的不安全迭代显示了不该做什么。 如果没有显式同步,在迭代期间进行并发修改可能会导致异常。 始终手动同步迭代。

复合操作示例

对同步列表的复合操作需要外部同步。 此示例展示了如何安全地执行涉及多个方法调用的操作。 演示了“检查然后操作”模式。

CompoundOperations.java
package com.zetcode;

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

public class CompoundOperations {

    public static void main(String[] args) {
        
        List<Integer> syncList = Collections.synchronizedList(new ArrayList<>());
        
        // Unsafe compound operation
        if (!syncList.contains(42)) {
            syncList.add(42); // Race condition possible
        }
        
        // Safe compound operation
        synchronized(syncList) {
            if (!syncList.contains(42)) {
                syncList.add(42);
            }
        }
        
        System.out.println("List contains 42: " + syncList.contains(42));
    }
}

此示例强调了复合操作需要外部同步。 不安全版本显示了 contains 和 add 调用之间的潜在竞争条件。

安全版本将这两个操作包装在同步块中。 这确保了复合操作的原子执行。 始终手动同步多步操作。

性能比较

此示例比较了同步列表与常规列表的性能。 同步增加了开销,但提供了线程安全性。 该测试衡量了性能影响。

PerformanceComparison.java
package com.zetcode;

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

public class PerformanceComparison {

    public static void main(String[] args) {
        
        final int ITERATIONS = 1000000;
        List<Integer> normalList = new ArrayList<>();
        List<Integer> syncList = Collections.synchronizedList(new ArrayList<>());
        
        // Test normal list
        long start = System.currentTimeMillis();
        for (int i = 0; i < ITERATIONS; i++) {
            normalList.add(i);
        }
        long normalTime = System.currentTimeMillis() - start;
        
        // Test synchronized list
        start = System.currentTimeMillis();
        for (int i = 0; i < ITERATIONS; i++) {
            syncList.add(i);
        }
        long syncTime = System.currentTimeMillis() - start;
        
        System.out.println("Normal list time: " + normalTime + "ms");
        System.out.println("Sync list time: " + syncTime + "ms");
        System.out.println("Overhead: " + (syncTime - normalTime) + "ms");
    }
}

此性能测试比较了同步和不同步列表操作。 同步版本由于锁定显示了可衡量的开销。 确切的差异取决于系统和 JVM 的特征。

输出结果展示了线程安全性和性能之间的权衡。 仅在需要线程安全时才使用同步列表。 考虑其他选项,如 CopyOnWriteArrayList,用于特定用例。

synchronizedList 的替代方案

此示例展示了 CopyOnWriteArrayList 作为 synchronizedList 的替代方案。 它提供线程安全性,而无需显式同步。 讨论了这些方法之间的权衡。

CopyOnWriteExample.java
package com.zetcode;

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

public class CopyOnWriteExample {

    public static void main(String[] args) {
        
        List<String> cowList = new CopyOnWriteArrayList<>();
        
        cowList.add("Java");
        cowList.add("Python");
        cowList.add("C++");
        
        // Safe iteration without synchronization
        for (String lang : cowList) {
            System.out.println(lang);
            cowList.add("JavaScript"); // Modification during iteration
        }
        
        System.out.println("Final list: " + cowList);
    }
}

CopyOnWriteArrayList 通过不同的机制提供线程安全性。 它在修改时创建底层数组的新副本。 这允许在没有同步的情况下安全地进行迭代。

该示例显示了在迭代期间进行修改,这将在使用同步列表时失败。 权衡是更高的内存使用率和写入开销。 根据您的特定要求进行选择。

实际用例

此示例演示了使用 synchronizedList 的真实场景。 我们模拟一个日志记录系统,其中多个线程添加日志条目。 同步列表确保了线程安全的日志记录。

LoggingSystem.java
package com.zetcode;

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

public class LoggingSystem {

    private final List<String> logEntries;
    
    public LoggingSystem() {
        this.logEntries = Collections.synchronizedList(new ArrayList<>());
    }
    
    public void addLogEntry(String entry) {
        logEntries.add(entry);
    }
    
    public void processLogs() {
        synchronized(logEntries) {
            for (String entry : logEntries) {
                System.out.println(entry);
            }
            logEntries.clear();
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        LoggingSystem logger = new LoggingSystem();
        
        // Simulate multiple threads logging
        Thread[] threads = new Thread[3];
        for (int i = 0; i < threads.length; i++) {
            final int threadId = i;
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 5; j++) {
                    logger.addLogEntry("Thread " + threadId + " - Message " + j);
                }
            });
            threads[i].start();
        }
        
        // Wait for threads to finish
        for (Thread t : threads) {
            t.join();
        }
        
        // Process logs
        logger.processLogs();
    }
}

此示例展示了 synchronizedList 在日志记录系统中的实际应用。 多个线程可以安全地并发添加日志条目。 processLogs 方法演示了正确的同步迭代和清除。

输出结果显示了来自不同线程的所有日志条目交错,但没有损坏。 这种模式在现实世界的并发应用程序中很常见。

来源

Java Collections.synchronizedList 文档

在本文中,我们深入探讨了 Java 的 Collections.synchronizedList 方法。 我们介绍了基本用法、多线程场景、迭代、性能和替代方案。 了解线程安全的集合对于并发编程至关重要。

作者

我叫 Jan Bodnar,是一位经验丰富的程序员,在该领域拥有多年的经验。 我从 2007 年开始撰写编程文章,此后撰写了 1,400 多篇文章和八本电子书。 凭借超过八年的教学经验,我致力于分享我的知识并帮助他人掌握编程概念。

列出所有Java教程