ZetCode

Java Collections.synchronizedSortedSet

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

Collections.synchronizedSortedSet 方法提供对已排序集合的线程安全访问。它返回一个由指定的已排序集合支持的同步(线程安全)已排序集合。此包装器确保安全的并发访问。

在多线程环境中,直接访问集合可能会导致问题。同步包装器通过同步所有操作来防止数据损坏。它是 Java Collections Framework 实用方法的一部分。

SortedSet 和同步基础知识

SortedSet 根据元素的自然顺序或比较器以升序维护元素。但是,诸如 TreeSet 之类的标准实现不是线程安全的。多线程访问需要同步。

Collections.synchronizedSortedSet 方法通过包装集合来解决此问题。对返回的集合的所有访问都与集合本身同步。这可以防止并发修改问题。

基本 SynchronizedSortedSet 示例

此示例演示如何从 TreeSet 创建同步的已排序集合。我们展示了诸如添加元素和迭代之类的基本操作。同步包装器确保线程安全。

BasicSynchronizedSortedSet.java
package com.zetcode;

import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;

public class BasicSynchronizedSortedSet {

    public static void main(String[] args) {
        
        // Create a regular TreeSet
        SortedSet<String> treeSet = new TreeSet<>();
        
        // Wrap it in a synchronized sorted set
        SortedSet<String> syncSet = Collections.synchronizedSortedSet(treeSet);
        
        // Add elements
        syncSet.add("Apple");
        syncSet.add("Banana");
        syncSet.add("Cherry");
        
        // Iterate (must synchronize manually for iteration)
        synchronized(syncSet) {
            for (String fruit : syncSet) {
                System.out.println(fruit);
            }
        }
        
        System.out.println("Size: " + syncSet.size());
    }
}

此代码围绕 TreeSet 创建一个同步包装器。现在对集合的所有操作都是线程安全的。请注意,迭代需要显式同步。输出显示按排序顺序排列的元素。

同步包装器确保安全的并发访问。但是,复合操作仍然需要外部同步。该示例演示了正确的迭代同步。

多线程访问示例

此示例显示了多个线程如何安全地访问同步的已排序集合。我们创建了两个并发添加元素的线程。同步包装器可以防止损坏。

MultiThreadedSortedSet.java
package com.zetcode;

import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;

public class MultiThreadedSortedSet {

    public static void main(String[] args) throws InterruptedException {
        
        SortedSet<Integer> syncSet = Collections.synchronizedSortedSet(
            new TreeSet<>());
        
        // Create and start two threads
        Thread t1 = new Thread(() -> addNumbers(syncSet, 0, 50));
        Thread t2 = new Thread(() -> addNumbers(syncSet, 50, 100));
        
        t1.start();
        t2.start();
        
        t1.join();
        t2.join();
        
        // Verify all numbers were added
        System.out.println("Total elements: " + syncSet.size());
        System.out.println("First: " + syncSet.first());
        System.out.println("Last: " + syncSet.last());
    }
    
    private static void addNumbers(SortedSet<Integer> set, int start, int end) {
        for (int i = start; i < end; i++) {
            set.add(i);
        }
    }
}

此示例演示了线程安全的并发访问。两个线程同时将数字添加到集合中。同步包装器可以防止竞争条件。最终输出显示已正确添加所有数字。

请注意,虽然单个操作是线程安全的,但复合操作可能需要额外的同步。该示例显示了使用 join() 的正确线程协调。

Comparator 与 SynchronizedSortedSet

此示例演示了将自定义比较器与同步的已排序集合一起使用。我们创建一个不区分大小写的已排序集合,并将其包装以实现线程安全。比较器定义排序顺序。

ComparatorSortedSet.java
package com.zetcode;

import java.util.Collections;
import java.util.Comparator;
import java.util.SortedSet;
import java.util.TreeSet;

public class ComparatorSortedSet {

    public static void main(String[] args) {
        
        // Create comparator for case-insensitive sorting
        Comparator<String> ignoreCase = String.CASE_INSENSITIVE_ORDER;
        
        // Create sorted set with comparator
        SortedSet<String> treeSet = new TreeSet<>(ignoreCase);
        
        // Wrap in synchronized set
        SortedSet<String> syncSet = Collections.synchronizedSortedSet(treeSet);
        
        // Add mixed-case elements
        syncSet.add("apple");
        syncSet.add("Banana");
        syncSet.add("CHERRY");
        syncSet.add("dATE");
        
        // Iterate (with synchronization)
        synchronized(syncSet) {
            for (String fruit : syncSet) {
                System.out.println(fruit);
            }
        }
    }
}

此代码创建一个不区分大小写的已排序集合,并将其包装以实现线程安全。比较器确保正确的排序,而与大小写无关。同步包装器使所有操作都线程安全。

输出显示元素按大小写不敏感的方式排序。同步确保安全访问,即使多个线程同时修改集合。

SynchronizedSortedSet 的批量操作

此示例演示了对同步的已排序集合执行的批量操作。我们展示了 addAllremoveAllretainAll 操作。每个操作都会自动同步。

BulkOperationsSortedSet.java
package com.zetcode;

import java.util.Arrays;
import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;

public class BulkOperationsSortedSet {

    public static void main(String[] args) {
        
        SortedSet<String> syncSet = Collections.synchronizedSortedSet(
            new TreeSet<>());
        
        // Add multiple elements
        syncSet.addAll(Arrays.asList("Apple", "Banana", "Cherry", "Date"));
        System.out.println("After addAll: " + syncSet);
        
        // Remove multiple elements
        syncSet.removeAll(Arrays.asList("Banana", "Date"));
        System.out.println("After removeAll: " + syncSet);
        
        // Retain only specified elements
        syncSet.retainAll(Arrays.asList("Apple", "Cherry", "Fig"));
        System.out.println("After retainAll: " + syncSet);
        
        // Check contains all
        boolean hasAll = syncSet.containsAll(Arrays.asList("Apple", "Cherry"));
        System.out.println("Contains all: " + hasAll);
    }
}

此示例显示了对同步的已排序集合的批量操作。由于同步包装器,每个操作都是线程安全的。输出演示了每个操作如何修改集合。

使用元素组时,批量操作特别有用。同步包装器确保这些操作保持原子性和线程安全性。

迭代和子集操作

此示例演示了子集操作和正确的迭代技术。我们展示了 subSetheadSettailSet 方法。迭代需要显式同步。

SubsetOperations.java
package com.zetcode;

import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;

public class SubsetOperations {

    public static void main(String[] args) {
        
        SortedSet<Integer> syncSet = Collections.synchronizedSortedSet(
            new TreeSet<>());
        
        // Add numbers 1-100
        for (int i = 1; i <= 100; i++) {
            syncSet.add(i);
        }
        
        // Get subset 40-60
        SortedSet<Integer> subSet = syncSet.subSet(40, 60);
        System.out.println("Subset 40-60 size: " + subSet.size());
        
        // Get headSet (elements < 25)
        SortedSet<Integer> headSet = syncSet.headSet(25);
        System.out.println("HeadSet <25 size: " + headSet.size());
        
        // Get tailSet (elements >= 75)
        SortedSet<Integer> tailSet = syncSet.tailSet(75);
        System.out.println("TailSet >=75 size: " + tailSet.size());
        
        // Iterate subset (with synchronization)
        synchronized(syncSet) {
            for (Integer num : subSet) {
                System.out.print(num + " ");
            }
        }
    }
}

此示例演示了对同步的已排序集合执行的子集操作。subSetheadSettailSet 方法创建原始集合的部分视图。这些视图也已同步。

输出显示不同子集的大小。同步迭代确保遍历期间的线程安全访问。子集操作对于处理排序数据的范围非常强大。

性能注意事项

此示例比较了同步和非同步的已排序集合之间的性能。我们测量两者的操作时间,以显示同步开销。该测试演示了何时需要同步。

PerformanceComparison.java
package com.zetcode;

import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;

public class PerformanceComparison {

    public static void main(String[] args) {
        
        final int ELEMENTS = 100000;
        
        // Unsynchronized set
        SortedSet<Integer> treeSet = new TreeSet<>();
        long start = System.currentTimeMillis();
        for (int i = 0; i < ELEMENTS; i++) {
            treeSet.add(i);
        }
        long unsyncTime = System.currentTimeMillis() - start;
        
        // Synchronized set
        SortedSet<Integer> syncSet = Collections.synchronizedSortedSet(
            new TreeSet<>());
        start = System.currentTimeMillis();
        for (int i = 0; i < ELEMENTS; i++) {
            syncSet.add(i);
        }
        long syncTime = System.currentTimeMillis() - start;
        
        System.out.println("Unsynchronized add time: " + unsyncTime + "ms");
        System.out.println("Synchronized add time: " + syncTime + "ms");
        System.out.println("Overhead: " + 
            ((double)(syncTime - unsyncTime)/unsyncTime * 100) + "%");
    }
}

此代码比较了同步和非同步的已排序集合之间的性能。该测试将许多元素添加到两个集合中,并测量所花费的时间。输出显示了同步开销百分比。

同步会增加开销,但对于线程安全是必要的。在单线程场景中,首选非同步集合。该示例有助于理解性能权衡。

来源

Java Collections.synchronizedSortedSet 文档

在本文中,我们深入探讨了 Collections.synchronizedSortedSet。我们介绍了基本用法、多线程、比较器、批量操作、子集和性能。理解这些概念对于线程安全的已排序集合操作至关重要。

作者

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

列出所有Java教程