ZetCode

Java Collections.synchronizedSortedMap

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

Collections.synchronizedSortedMap 方法为 SortedMap 实现提供了线程安全的包装器。它返回一个由指定的排序映射支持的同步(线程安全)排序映射。

此方法对于多线程环境至关重要,在多线程环境中,多个线程访问相同的排序映射。它确保了原子操作并防止数据损坏。所有访问都必须通过返回的同步映射进行。

SortedMap 接口概述

SortedMap 扩展了 Map 接口,以在其键上提供排序。映射根据其键的自然排序或由比较器进行排序。它提供了获取子映射和访问第一个/最后一个键的方法。

关键方法包括 comparatorfirstKeylastKeysubMapheadMaptailMap。实现包括 TreeMap

synchronizedSortedMap 的基本用法

此示例演示了如何从 TreeMap 创建一个同步的排序映射。我们执行基本的 put、get 和迭代操作。对于所有这些操作,映射都保持线程安全。

SynchronizedSortedMapBasic.java
package com.zetcode;

import java.util.Collections;
import java.util.SortedMap;
import java.util.TreeMap;

public class SynchronizedSortedMapBasic {

    public static void main(String[] args) {
        
        // Create a TreeMap
        SortedMap<String, Integer> treeMap = new TreeMap<>();
        
        // Create synchronized wrapper
        SortedMap<String, Integer> syncMap = 
            Collections.synchronizedSortedMap(treeMap);
        
        // Add elements
        syncMap.put("Apple", 10);
        syncMap.put("Banana", 20);
        syncMap.put("Cherry", 30);
        
        // Get element
        System.out.println("Banana quantity: " + syncMap.get("Banana"));
        
        // Iterate (must synchronize manually)
        synchronized(syncMap) {
            for (var entry : syncMap.entrySet()) {
                System.out.println(entry.getKey() + ": " + entry.getValue());
            }
        }
    }
}

此代码从 TreeMap 创建一个同步的排序映射。我们添加元素、检索值并安全地进行迭代。请注意,迭代需要在映射对象上进行显式同步。

输出以排序顺序显示映射内容。同步确保了所有操作期间的线程安全。

多线程访问示例

此示例演示了从多个线程对同步排序映射的线程安全访问。我们创建生产者和消费者线程,它们修改和读取共享映射。

SynchronizedSortedMapThreads.java
package com.zetcode;

import java.util.Collections;
import java.util.SortedMap;
import java.util.TreeMap;

public class SynchronizedSortedMapThreads {

    public static void main(String[] args) throws InterruptedException {
        
        SortedMap<Integer, String> syncMap = 
            Collections.synchronizedSortedMap(new TreeMap<>());
        
        // Producer thread
        Thread producer = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                syncMap.put(i, "Value" + i);
                System.out.println("Added: " + i);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        });
        
        // Consumer thread
        Thread consumer = new Thread(() -> {
            synchronized(syncMap) {
                for (var entry : syncMap.entrySet()) {
                    System.out.println("Read: " + entry.getKey());
                    try {
                        Thread.sleep(150);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
            }
        });
        
        producer.start();
        Thread.sleep(50); // Let producer add first element
        consumer.start();
        
        producer.join();
        consumer.join();
        
        System.out.println("Final map: " + syncMap);
    }
}

此示例展示了对同步排序映射的安全并发访问。生产者添加元素,而消费者读取它们。同步可以防止并发修改异常。

请注意迭代期间的显式同步。输出显示了添加和读取操作的交错,没有数据损坏。

带有同步的子映射操作

此示例演示了如何将子映射操作与同步的排序映射一起使用。我们创建一个子映射并对其执行操作,同时保持线程安全。

SynchronizedSubMap.java
package com.zetcode;

import java.util.Collections;
import java.util.SortedMap;
import java.util.TreeMap;

public class SynchronizedSubMap {

    public static void main(String[] args) {
        
        SortedMap<Integer, String> syncMap = 
            Collections.synchronizedSortedMap(new TreeMap<>());
        
        // Populate map
        syncMap.put(1, "One");
        syncMap.put(2, "Two");
        syncMap.put(3, "Three");
        syncMap.put(4, "Four");
        syncMap.put(5, "Five");
        
        // Get submap (2 <= key < 4)
        SortedMap<Integer, String> subMap;
        synchronized(syncMap) {
            subMap = syncMap.subMap(2, 4);
        }
        
        // Must synchronize on original map for submap operations
        synchronized(syncMap) {
            System.out.println("SubMap contents:");
            subMap.forEach((k, v) -> System.out.println(k + ": " + v));
            
            // Modify submap
            subMap.put(3, "THREE");
        }
        
        System.out.println("Original map after modification: " + syncMap);
    }
}

此代码展示了如何使用同步排序映射的子映射。我们创建一个子映射并执行操作,同时保持同步。请注意,子映射操作需要在原始映射上进行同步。

输出表明对子映射的更改会影响原始映射。当正确同步时,所有操作都保持线程安全。

带有同步排序映射的比较器

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

SynchronizedComparator.java
package com.zetcode;

import java.util.Collections;
import java.util.Comparator;
import java.util.SortedMap;
import java.util.TreeMap;

public class SynchronizedComparator {

    public static void main(String[] args) {
        
        // Create case-insensitive comparator
        Comparator<String> caseInsensitive = String.CASE_INSENSITIVE_ORDER;
        
        // Create TreeMap with comparator
        SortedMap<String, Integer> treeMap = new TreeMap<>(caseInsensitive);
        
        // Create synchronized wrapper
        SortedMap<String, Integer> syncMap = 
            Collections.synchronizedSortedMap(treeMap);
        
        // Add mixed-case keys
        syncMap.put("Apple", 1);
        syncMap.put("banana", 2);
        syncMap.put("CHERRY", 3);
        
        // Access with different cases
        System.out.println("apple: " + syncMap.get("apple"));
        System.out.println("BANANA: " + syncMap.get("BANANA"));
        System.out.println("cherry: " + syncMap.get("cherry"));
        
        // Show sorted order
        synchronized(syncMap) {
            System.out.println("Sorted entries:");
            syncMap.forEach((k, v) -> System.out.println(k + ": " + v));
        }
    }
}

此示例创建一个不区分大小写的同步排序映射。我们添加具有不同大小写的元素,并演示不区分大小写的查找。映射根据比较器保持其排序。

输出显示成功的查找,无论大小写如何。同步包装器确保了线程安全,同时保留了自定义排序。

性能注意事项

此示例比较了在并发访问下同步和不同步排序映射的性能。我们使用多个线程来测量操作时间。

SynchronizedPerformance.java
package com.zetcode;

import java.util.Collections;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class SynchronizedPerformance {

    static final int THREADS = 10;
    static final int OPERATIONS = 10000;

    public static void main(String[] args) throws InterruptedException {
        
        // Unsynchronized map (will produce incorrect results)
        SortedMap unsafeMap = new TreeMap<>();
        
        // Synchronized map
        SortedMap safeMap = 
            Collections.synchronizedSortedMap(new TreeMap<>());
        
        // Test unsafe map
        long unsafeTime = testMap(unsafeMap);
        System.out.println("Unsafe map count: " + unsafeMap.size() + 
            " (should be " + (THREADS * OPERATIONS) + ")");
        System.out.println("Unsafe map time: " + unsafeTime + "ms");
        
        // Test safe map
        long safeTime = testMap(safeMap);
        System.out.println("Safe map count: " + safeMap.size() + 
            " (correct)");
        System.out.println("Safe map time: " + safeTime + "ms");
    }
    
    static long testMap(SortedMap map) 
            throws InterruptedException {
            
        ExecutorService executor = Executors.newFixedThreadPool(THREADS);
        long start = System.currentTimeMillis();
        
        for (int i = 0; i < THREADS; i++) {
            executor.execute(() -> {
                for (int j = 0; j < OPERATIONS; j++) {
                    map.put(j, j);
                }
            });
        }
        
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);
        
        return System.currentTimeMillis() - start;
    }
}

此代码比较了同步和不同步映射之间的性能。不同步版本无法在并发访问下保持正确状态。同步版本较慢,但正确。

输出显示了性能和正确性之间的权衡。同步映射以牺牲一些性能为代价确保了数据完整性。

第一个和最后一个键操作

此示例演示了对同步排序映射中第一个和最后一个键的线程安全访问。我们展示了如何安全地检索这些特殊条目。

SynchronizedFirstLast.java
package com.zetcode;

import java.util.Collections;
import java.util.SortedMap;
import java.util.TreeMap;

public class SynchronizedFirstLast {

    public static void main(String[] args) {
        
        SortedMap syncMap = 
            Collections.synchronizedSortedMap(new TreeMap<>());
        
        syncMap.put("A", 1.1);
        syncMap.put("B", 2.2);
        syncMap.put("C", 3.3);
        syncMap.put("D", 4.4);
        
        // Safely access first and last keys
        String firstKey, lastKey;
        synchronized(syncMap) {
            firstKey = syncMap.firstKey();
            lastKey = syncMap.lastKey();
        }
        
        System.out.println("First key: " + firstKey);
        System.out.println("Last key: " + lastKey);
        
        // Safely get first and last entries
        synchronized(syncMap) {
            System.out.println("First entry: " + 
                syncMap.firstKey() + "=" + syncMap.get(firstKey));
            System.out.println("Last entry: " + 
                syncMap.lastKey() + "=" + syncMap.get(lastKey));
        }
    }
}

此示例展示了如何安全地访问同步排序映射中的第一个和最后一个键。我们演示了键检索和值访问,同时保持了线程安全。

输出显示了预期的第一个和最后一个条目。适当的同步确保了这些操作是原子且线程安全的。

headMap 和 tailMap 操作

此示例演示了 headMap 和 tailMap 操作的线程安全用法。我们创建了映射的各个部分的视图,并对其执行操作。

SynchronizedHeadTail.java
package com.zetcode;

import java.util.Collections;
import java.util.SortedMap;
import java.util.TreeMap;

public class SynchronizedHeadTail {

    public static void main(String[] args) {
        
        SortedMap<Integer, String> syncMap = 
            Collections.synchronizedSortedMap(new TreeMap<>());
        
        for (int i = 1; i <= 10; i++) {
            syncMap.put(i, "Value" + i);
        }
        
        // Get head map (keys less than 5)
        SortedMap<Integer, String> headMap;
        synchronized(syncMap) {
            headMap = syncMap.headMap(5);
        }
        
        // Get tail map (keys greater than or equal to 7)
        SortedMap<Integer, String> tailMap;
        synchronized(syncMap) {
            tailMap = syncMap.tailMap(7);
        }
        
        // Modify through views (must synchronize on original map)
        synchronized(syncMap) {
            headMap.put(4, "MODIFIED");
            tailMap.remove(8);
        }
        
        System.out.println("Head map (1-4): " + headMap);
        System.out.println("Tail map (7-10): " + tailMap);
        System.out.println("Original map: " + syncMap);
    }
}

此示例演示了如何安全地将 headMap 和 tailMap 操作与同步排序映射一起使用。我们创建了映射的视图(headMap 用于小于 5 的键,tailMap 用于大于或等于 7 的键),并通过这些视图执行修改。在原始映射上进行同步对于保持线程安全至关重要。

输出显示了修改后的 headMap 和 tailMap 的内容,以及更新的原始映射。这些操作是线程安全的,并在视图和原始映射中一致地反映更改。

实际用例:线程安全缓存

此示例演示了 Collections.synchronizedSortedMap 在线程安全缓存实现中的实际应用。缓存存储带有时间戳的键值对,并确保跨多个线程进行线程安全的访问。

SynchronizedCache.java
package com.zetcode;

import java.util.Collections;
import java.util.SortedMap;
import java.util.TreeMap;

public class SynchronizedCache {

    private final SortedMap cache;

    public SynchronizedCache() {
        cache = Collections.synchronizedSortedMap(new TreeMap<>());
    }

    public void put(String value) {
        cache.put(System.currentTimeMillis(), value);
    }

    public String getLatest() {
        synchronized (cache) {
            return cache.isEmpty() ? null : cache.get(cache.lastKey());
        }
    }

    public SortedMap getRange(Long from, Long to) {
        synchronized (cache) {
            return cache.subMap(from, to);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynchronizedCache cache = new SynchronizedCache();

        // Producer thread adding cache entries
        Thread producer = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                cache.put("Data" + i);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        });

        // Consumer thread reading cache entries
        Thread consumer = new Thread(() -> {
            try {
                Thread.sleep(300); // Wait for some data
                System.out.println("Latest entry: " + cache.getLatest());
                synchronized (cache) {
                    Long firstKey = cache.firstKey();
                    Long lastKey = cache.lastKey();
                    System.out.println("Range of entries: " + 
                        cache.getRange(firstKey, lastKey + 1));
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        producer.start();
        consumer.start();

        producer.join();
        consumer.join();

        System.out.println("Final cache state: " + cache);
    }
}

此示例使用同步排序映射实现线程安全缓存。缓存存储带有时间戳的值作为键,允许检索最新的条目或一系列条目。生产者线程添加数据,而消费者线程读取最新的条目和一系列条目。

输出显示了消费者检索到的最新条目、一系列条目以及缓存的最终状态。所有操作都是线程安全的,这演示了 Collections.synchronizedSortedMap 在多线程环境中的实际应用。

来源

Java Collections.synchronizedSortedMap 文档

在本教程中,我们深入探讨了 Collections.synchronizedSortedMap。我们涵盖了基本用法、多线程访问、子映射操作、自定义比较器、性能考虑因素、第一个/最后一个键操作、head/tail 映射操作以及实际的缓存实现。此方法对于确保排序映射操作中的线程安全非常重要,尤其是在多线程应用程序中。

作者

我的名字是 Jan Bodnar,我是一位经验丰富的程序员,在该领域拥有多年经验。我从 2007 年开始撰写编程文章,此后撰写了 1,400 多篇文章和 8 本电子书。凭借超过 8 年的教学经验,我致力于分享我的知识并帮助他人掌握编程概念。

列出所有Java教程