ZetCode

Java Collections.synchronizedCollection

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

Collections.synchronizedCollection 方法创建线程安全的集合包装器。它返回由指定集合支持的同步(线程安全)集合。这对于多线程环境至关重要。

对返回的集合的所有访问都必须通过同步包装器。包装器确保所有方法调用都是原子性的。但是,对于复合操作仍然需要手动同步。

Collections.synchronizedCollection 概述

synchronizedCollection 方法是 Java Collections 实用程序类的一部分。它为集合操作提供基本的线程安全性。该方法使用同步包装任何 Collection 实现。

返回的集合序列化所有方法访问。这可以防止并发修改问题。但是,迭代需要显式同步以避免 ConcurrentModificationException。

基本同步集合

此示例演示如何创建基本同步集合。我们用 Collections.synchronizedCollection 包装一个 ArrayList。该示例展示了同步集合上的基本操作。

BasicSynchronizedCollection.java
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 的所有操作都是线程安全的。该示例演示了添加、删除和检查元素大小的操作。

输出显示每次操作后集合的状态。同步包装器确保从多个线程访问时,这些操作是原子性的。

多线程访问

此示例显示多个线程如何安全地访问同步集合。我们创建多个线程并发地修改集合。同步包装器可防止数据损坏。

MultiThreadSynchronizedCollection.java
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 个元素)。如果没有同步,由于竞争条件,计数将是不可预测的。

使用同步进行迭代

迭代同步集合需要显式同步。此示例显示了在保持线程安全性的同时进行迭代的正确方法。在整个迭代过程中,集合被锁定。

SynchronizedIteration.java
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 方法更简洁,但对于复杂操作可能不太灵活。

复合操作

同步集合上的复合操作需要额外的同步。此示例演示了原子地检查然后添加元素。必须同步整个操作才能保证线程安全。

CompoundOperations.java
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)。它演示了每种方法的性能差异和用例。

SyncVsConcurrent.java
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");
    }
}

此示例针对并发集合对同步集合进行基准测试。同步集合使用粗粒度锁定,而并发集合使用更复杂的技术。性能特征因用例而异。

对于具有简单操作的写密集型工作负载,同步集合通常更好。对于读密集型工作负载或复杂操作,并发集合通常表现更好。

具有自定义对象的同步集合

此示例演示了如何将同步集合与自定义对象一起使用。它展示了如何确保当从多个线程访问集合元素本身时保证线程安全。

CustomObjectsInSyncCollection.java
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 类必须处理自己的同步以实现线程安全的元素访问。

同步集合性能注意事项

此示例演示了同步集合的性能影响。它显示了同步开销如何影响单线程和多线程场景中的操作。

SyncCollectionPerformance.java
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 方法。我们涵盖了基本用法、多线程访问、迭代、复合操作和性能注意事项。理解这些概念对于开发线程安全应用程序至关重要。

作者

我叫 Jan Bodnar,是一名经验丰富的程序员。我于 2007 年开始撰写编程文章,至今已创作了 1,400 多篇文章和八本电子书。凭借超过八年的教学经验,我致力于分享我的知识并帮助他人掌握编程概念。

列出所有Java教程