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教程。