Java Collections.synchronizedList 方法
上次修改时间:2025 年 4 月 20 日
Collections.synchronizedList
方法提供对 List 实现的线程安全访问。 它返回由指定的列表支持的同步(线程安全)列表。 所有操作都是同步的,防止并发修改问题。
此方法是 Java 集合框架实用程序类的一部分。 它包装现有的 List 实现以使其线程安全。 返回的列表必须手动同步以进行迭代。
Collections.synchronizedList 概述
synchronizedList
方法创建一个围绕 List 的线程安全包装器。 它同步所有单独的操作,如添加、删除和获取。 这可以防止在多线程环境中出现数据损坏。
虽然单个操作是线程安全的,但复合操作需要外部同步。 迭代也需要手动同步以防止 ConcurrentModificationException。 该方法在 Collections 实用程序类中声明。
基本的 synchronizedList 示例
此示例演示了如何从 ArrayList 创建一个同步列表。 我们执行基本操作以显示线程安全的访问。 该示例展示了如何创建和使用同步列表。
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 这样的单个操作是原子的。
多线程访问示例
此示例演示了从多个线程对同步列表进行线程安全的访问。 我们创建了多个并发修改列表的线程。 同步包装器可防止数据损坏。
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 个元素)。 同步列表确保在并发修改期间进行线程安全的访问。
迭代同步列表
迭代同步列表需要手动同步。 虽然单个操作是线程安全的,但迭代涉及多个操作。 此示例展示了正确的迭代方式。
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); // } } }
此示例演示了迭代同步列表的正确方法。 整个迭代块被包装在一个同步块中,使用列表作为锁。
注释掉的不安全迭代显示了不该做什么。 如果没有显式同步,在迭代期间进行并发修改可能会导致异常。 始终手动同步迭代。
复合操作示例
对同步列表的复合操作需要外部同步。 此示例展示了如何安全地执行涉及多个方法调用的操作。 演示了“检查然后操作”模式。
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 调用之间的潜在竞争条件。
安全版本将这两个操作包装在同步块中。 这确保了复合操作的原子执行。 始终手动同步多步操作。
性能比较
此示例比较了同步列表与常规列表的性能。 同步增加了开销,但提供了线程安全性。 该测试衡量了性能影响。
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 的替代方案。 它提供线程安全性,而无需显式同步。 讨论了这些方法之间的权衡。
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 的真实场景。 我们模拟一个日志记录系统,其中多个线程添加日志条目。 同步列表确保了线程安全的日志记录。
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
方法。 我们介绍了基本用法、多线程场景、迭代、性能和替代方案。 了解线程安全的集合对于并发编程至关重要。
作者
列出所有Java教程。