Java Collections.synchronizedSet 方法
上次修改时间:2025 年 4 月 20 日
Collections.synchronizedSet
方法是 Java Collections Framework 中的一个实用方法。它返回一个由指定的 set 支持的同步(线程安全)的 set。这个包装器为基本操作提供线程安全保障。
对返回的 set 的所有访问都必须通过同步包装器进行。该方法确保单个操作是原子且线程安全的。但是,复合操作需要外部同步。
Collections.synchronizedSet 概述
synchronizedSet
方法是 java.util.Collections
类的一部分。它接受一个 Set
作为输入,并返回一个线程安全的版本。返回的 set 的迭代器需要手动同步以保证线程安全。
当您需要线程安全性,但又不想使用基于 ConcurrentHashMap
的 set 时,此方法很有用。它更简单,但与并发集合相比,在高并发情况下性能可能较低。
基本 synchronizedSet 示例
此示例演示了如何从 HashSet 创建一个同步的 set。我们执行添加、删除和包含等基本操作。该示例展示了基本的线程安全包装器用法。
package com.zetcode; import java.util.Collections; import java.util.HashSet; import java.util.Set; public class SynchronizedSetBasic { public static void main(String[] args) { Set<String> normalSet = new HashSet<>(); Set<String> syncSet = Collections.synchronizedSet(normalSet); // Add elements to synchronized set syncSet.add("Apple"); syncSet.add("Banana"); syncSet.add("Cherry"); // Check size and contents System.out.println("Size: " + syncSet.size()); System.out.println("Contains Banana: " + syncSet.contains("Banana")); // Remove element syncSet.remove("Apple"); System.out.println("After removal: " + syncSet); } }
此代码创建一个围绕 HashSet 的同步 set 包装器。对同步 set 的所有操作都是线程安全的。该示例展示了现在受到保护,免受并发访问的基本操作。
输出结果表明,同步 set 在单线程操作中表现得像一个普通 set。线程安全在多线程场景中变得明显。
多线程访问示例
此示例演示了 synchronizedSet
如何处理来自多个线程的并发访问。我们创建了几个同时修改 set 的线程。同步包装器可以防止数据损坏。
package com.zetcode; import java.util.Collections; import java.util.HashSet; import java.util.Set; public class SynchronizedSetThreads { public static void main(String[] args) throws InterruptedException { Set<Integer> numbers = Collections.synchronizedSet(new HashSet<>()); // 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 < 1000; j++) { numbers.add(threadId * 1000 + j); } }); threads[i].start(); } // Wait for all threads to complete for (Thread thread : threads) { thread.join(); } System.out.println("Final set size: " + numbers.size()); } }
此示例显示了 synchronizedSet 的线程安全特性。多个线程并发添加元素而不会导致损坏。每个添加操作都是原子的,并受到同步的保护。
最终大小应为 5000(5 个线程 × 1000 次添加)。如果没有同步,由于更新丢失,大小可能会更小。
迭代同步 Set
迭代同步 set 需要手动同步。此示例演示了在保持线程安全的同时进行迭代的正确方法。如果没有外部同步,迭代器本身不是线程安全的。
package com.zetcode; import java.util.Collections; import java.util.HashSet; import java.util.Set; public class SynchronizedSetIteration { public static void main(String[] args) { Set<String> colors = Collections.synchronizedSet(new HashSet<>()); colors.add("Red"); colors.add("Green"); colors.add("Blue"); // Proper synchronized iteration synchronized(colors) { for (String color : colors) { System.out.println(color); } } // Unsafe iteration (may throw ConcurrentModificationException) try { for (String color : colors) { System.out.println(color); } } catch (Exception e) { System.out.println("Exception during unsafe iteration: " + e); } } }
该示例展示了安全和不安全的迭代方法。同步块确保在迭代期间不会发生修改。如果没有同步,并发修改可能会导致异常。
在迭代同步集合时,始终使用手动同步。这可以防止 ConcurrentModificationException
并确保一致的视图。
复合操作示例
对同步 set 的复合操作需要额外的同步。此示例演示了如何安全地执行“检查然后执行”操作。单个操作是线程安全的,但序列需要保护。
package com.zetcode; import java.util.Collections; import java.util.HashSet; import java.util.Set; public class SynchronizedSetCompound { public static void main(String[] args) { Set<String> users = Collections.synchronizedSet(new HashSet<>()); // Unsafe compound operation if (!users.contains("Admin")) { users.add("Admin"); // Race condition possible } // Safe compound operation synchronized(users) { if (!users.contains("Admin")) { users.add("Admin"); } } System.out.println("Users: " + users); } }
该示例展示了复合操作的不安全和安全版本。不安全版本可能仍允许重复添加,这是由于竞态条件导致的。安全版本使用外部同步来保护整个操作序列。
执行必须是原子的多个操作时,始终手动同步。synchronized set 仅保护单个方法调用。
性能比较
此示例比较了 synchronizedSet 与常规 HashSet 和 ConcurrentHashMap 的 keySet 的性能。它演示了同步的开销。结果会因系统和争用级别的不同而异。
package com.zetcode; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; public class SynchronizedSetPerformance { public static void main(String[] args) { final int ITERATIONS = 1_000_000; Set<Integer> hashSet = new HashSet<>(); Set<Integer> syncSet = Collections.synchronizedSet(new HashSet<>()); Set<Integer> concurrentSet = ConcurrentHashMap.newKeySet(); // Test HashSet (unsynchronized) long start = System.currentTimeMillis(); for (int i = 0; i < ITERATIONS; i++) { hashSet.add(i); } System.out.println("HashSet time: " + (System.currentTimeMillis() - start) + "ms"); // Test synchronizedSet start = System.currentTimeMillis(); for (int i = 0; i < ITERATIONS; i++) { syncSet.add(i); } System.out.println("synchronizedSet time: " + (System.currentTimeMillis() - start) + "ms"); // Test ConcurrentHashMap's keySet start = System.currentTimeMillis(); for (int i = 0; i < ITERATIONS; i++) { concurrentSet.add(i); } System.out.println("ConcurrentSet time: " + (System.currentTimeMillis() - start) + "ms"); } }
此性能测试显示了不同线程安全 set 实现的相对开销。在争用情况下,synchronizedSet
通常显示比 ConcurrentHashMap
的 set 更高的开销。
结果有助于根据性能要求在同步方法之间进行选择。对于读取繁重的工作负载,synchronizedSet
可能足够了。
真实世界的用法示例
此示例演示了 synchronizedSet
的一个实际用例——跟踪 Web 应用程序中的活动用户会话。由于会话是并发添加/删除的,因此 set 需要是线程安全的。
package com.zetcode; import java.util.Collections; import java.util.Set; import java.util.HashSet; public class SessionTracker { private final Set<String> activeSessions = Collections.synchronizedSet(new HashSet<>()); public void addSession(String sessionId) { activeSessions.add(sessionId); System.out.println("Session added: " + sessionId); } public void removeSession(String sessionId) { activeSessions.remove(sessionId); System.out.println("Session removed: " + sessionId); } public int getActiveSessionCount() { return activeSessions.size(); } public static void main(String[] args) { SessionTracker tracker = new SessionTracker(); // Simulate concurrent session activity new Thread(() -> { tracker.addSession("user1"); tracker.addSession("user2"); try { Thread.sleep(100); } catch (InterruptedException e) {} tracker.removeSession("user1"); }).start(); new Thread(() -> { tracker.addSession("user3"); try { Thread.sleep(50); } catch (InterruptedException e) {} tracker.removeSession("user3"); tracker.addSession("user4"); }).start(); try { Thread.sleep(200); } catch (InterruptedException e) {} System.out.println("Active sessions: " + tracker.getActiveSessionCount()); } }
此示例模拟了 Web 应用程序会话跟踪器。同步 set 确保了线程安全的会话管理。多个线程可以安全地并发添加和删除会话。
输出结果显示了会话被添加和删除。最终计数正确地反映了剩余的活动会话。这种模式在服务器应用程序中很常见。
来源
Java Collections.synchronizedSet 文档
在本教程中,我们深入探讨了 Collections.synchronizedSet
方法。我们涵盖了基本用法、线程安全、迭代、复合操作、性能和实际应用。这个包装器为 set 操作提供了简单的线程安全保障。
作者
列出所有Java教程。