Java Collections.synchronizedCollection
上次修改时间:2025 年 4 月 20 日
Collections.synchronizedCollection
方法创建线程安全的集合包装器。它返回由指定集合支持的同步(线程安全)集合。这对于多线程环境至关重要。
对返回的集合的所有访问都必须通过同步包装器。包装器确保所有方法调用都是原子性的。但是,对于复合操作仍然需要手动同步。
Collections.synchronizedCollection 概述
synchronizedCollection
方法是 Java Collections 实用程序类的一部分。它为集合操作提供基本的线程安全性。该方法使用同步包装任何 Collection 实现。
返回的集合序列化所有方法访问。这可以防止并发修改问题。但是,迭代需要显式同步以避免 ConcurrentModificationException。
基本同步集合
此示例演示如何创建基本同步集合。我们用 Collections.synchronizedCollection
包装一个 ArrayList。该示例展示了同步集合上的基本操作。
package com.zetcode; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; public class BasicSynchronizedCollection { public static void main(String[] args) { Collection<String> baseCollection = new ArrayList<>(); Collection<String> syncCollection = Collections.synchronizedCollection(baseCollection); // Add elements in a thread-safe manner syncCollection.add("Java"); syncCollection.add("Python"); syncCollection.add("C++"); System.out.println("Synchronized collection: " + syncCollection); // Remove an element syncCollection.remove("Python"); System.out.println("After removal: " + syncCollection); // Check size System.out.println("Size: " + syncCollection.size()); } }
此代码创建一个围绕 ArrayList 的同步包装器。对 syncCollection
的所有操作都是线程安全的。该示例演示了添加、删除和检查元素大小的操作。
输出显示每次操作后集合的状态。同步包装器确保从多个线程访问时,这些操作是原子性的。
多线程访问
此示例显示多个线程如何安全地访问同步集合。我们创建多个线程并发地修改集合。同步包装器可防止数据损坏。
package com.zetcode; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; public class MultiThreadSynchronizedCollection { public static void main(String[] args) throws InterruptedException { Collection<Integer> numbers = Collections.synchronizedCollection(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 thread : threads) { thread.join(); } System.out.println("Total elements: " + numbers.size()); } }
此示例演示了线程安全的集合访问。五个线程每个线程并发地向集合中添加 100 个元素。同步包装器确保所有添加都得到正确序列化。
最终计数应恰好为 500 个元素(5 个线程 × 每个线程 100 个元素)。如果没有同步,由于竞争条件,计数将是不可预测的。
使用同步进行迭代
迭代同步集合需要显式同步。此示例显示了在保持线程安全性的同时进行迭代的正确方法。在整个迭代过程中,集合被锁定。
package com.zetcode; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; public class SynchronizedIteration { public static void main(String[] args) { Collection<String> syncCollection = Collections.synchronizedCollection(new ArrayList<>()); syncCollection.add("Apple"); syncCollection.add("Banana"); syncCollection.add("Cherry"); // Proper synchronized iteration synchronized (syncCollection) { for (String item : syncCollection) { System.out.println(item); } } // Alternative with forEach (Java 8+) syncCollection.forEach(System.out::println); } }
此示例演示了两种迭代方法。第一种使用显式同步来防止迭代期间的并发修改。第二种使用 forEach 方法,该方法在内部同步。
这两种方法都是线程安全的,但同步块提供了更多控制。forEach 方法更简洁,但对于复杂操作可能不太灵活。
复合操作
同步集合上的复合操作需要额外的同步。此示例演示了原子地检查然后添加元素。必须同步整个操作才能保证线程安全。
package com.zetcode; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; public class CompoundOperations { public static void main(String[] args) { Collection<String> syncCollection = Collections.synchronizedCollection(new ArrayList<>()); // Add some initial elements syncCollection.add("Red"); syncCollection.add("Green"); // Thread-safe compound operation synchronized (syncCollection) { if (!syncCollection.contains("Blue")) { syncCollection.add("Blue"); } } System.out.println("Collection: " + syncCollection); } }
此示例显示了一种常见模式:检查然后执行。必须原子地执行 contains 检查和随后的 add 操作,以防止竞争条件。同步块确保在这些操作之间没有其他线程可以修改集合。
如果没有此同步,则另一个线程可能会在我们检查和添加之间添加“Blue”。这将导致重复元素或其他不一致。
同步集合与并发集合
此示例比较了同步集合和并发集合(如 CopyOnWriteArrayList)。它演示了每种方法的性能差异和用例。
package com.zetcode; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.concurrent.CopyOnWriteArrayList; public class SyncVsConcurrent { public static void main(String[] args) { // Synchronized collection Collection<Integer> syncCollection = Collections.synchronizedCollection(new ArrayList<>()); // Concurrent collection Collection<Integer> concurrentCollection = new CopyOnWriteArrayList<>(); long start, end; // Test synchronized collection start = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { syncCollection.add(i); } end = System.currentTimeMillis(); System.out.println("Synchronized collection time: " + (end - start) + "ms"); // Test concurrent collection start = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { concurrentCollection.add(i); } end = System.currentTimeMillis(); System.out.println("Concurrent collection time: " + (end - start) + "ms"); } }
此示例针对并发集合对同步集合进行基准测试。同步集合使用粗粒度锁定,而并发集合使用更复杂的技术。性能特征因用例而异。
对于具有简单操作的写密集型工作负载,同步集合通常更好。对于读密集型工作负载或复杂操作,并发集合通常表现更好。
具有自定义对象的同步集合
此示例演示了如何将同步集合与自定义对象一起使用。它展示了如何确保当从多个线程访问集合元素本身时保证线程安全。
package com.zetcode; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; class Product { private String name; private double price; public Product(String name, double price) { this.name = name; this.price = price; } public synchronized void updatePrice(double newPrice) { this.price = newPrice; } @Override public synchronized String toString() { return name + ": $" + price; } } public class CustomObjectsInSyncCollection { public static void main(String[] args) { Collection<Product> products = Collections.synchronizedCollection(new ArrayList<>()); // Add products products.add(new Product("Laptop", 999.99)); products.add(new Product("Phone", 699.99)); // Update prices from multiple threads Thread t1 = new Thread(() -> { for (Product p : products) { p.updatePrice(p.toString().contains("Laptop") ? 899.99 : 599.99); } }); Thread t2 = new Thread(() -> { synchronized (products) { for (Product p : products) { System.out.println(p); } } }); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } } }
此示例显示了自定义 Product 对象的集合。Product 类有自己的同步来进行价格更新。集合包装器确保对集合结构的线程安全访问。
请注意,同步集合不会自动同步对元素的访问。Product 类必须处理自己的同步以实现线程安全的元素访问。
同步集合性能注意事项
此示例演示了同步集合的性能影响。它显示了同步开销如何影响单线程和多线程场景中的操作。
package com.zetcode; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; public class SyncCollectionPerformance { public static void main(String[] args) { final int ELEMENTS = 100000; // Unsynchronized collection Collection<Integer> normalCollection = new ArrayList<>(); // Synchronized collection Collection<Integer> syncCollection = Collections.synchronizedCollection(new ArrayList<>()); // Test unsynchronized add long start = System.currentTimeMillis(); for (int i = 0; i < ELEMENTS; i++) { normalCollection.add(i); } long end = System.currentTimeMillis(); System.out.println("Normal collection add time: " + (end - start) + "ms"); // Test synchronized add start = System.currentTimeMillis(); for (int i = 0; i < ELEMENTS; i++) { syncCollection.add(i); } end = System.currentTimeMillis(); System.out.println("Synchronized collection add time: " + (end - start) + "ms"); // Test multi-threaded synchronized add syncCollection.clear(); start = System.currentTimeMillis(); Thread[] threads = new Thread[4]; for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(() -> { for (int j = 0; j < ELEMENTS/threads.length; j++) { syncCollection.add(j); } }); threads[i].start(); } for (Thread t : threads) { try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } } end = System.currentTimeMillis(); System.out.println("Multi-threaded sync collection add time: " + (end - start) + "ms"); } }
此示例对同步集合性能进行基准测试。它比较了同步集合和非同步集合之间的单线程操作。它还测量了同步集合的多线程性能。
结果表明,同步在单线程场景中会增加开销。但是,在多线程环境中,同步可防止数据损坏并确保线程安全,但会牺牲一些性能。
来源
Java Collections.synchronizedCollection 文档
在本文中,我们深入探讨了 Java 的 Collections.synchronizedCollection
方法。我们涵盖了基本用法、多线程访问、迭代、复合操作和性能注意事项。理解这些概念对于开发线程安全应用程序至关重要。
作者
列出所有Java教程。