Java Collections.synchronizedCollection
Last modified: April 20, 2025
The Collections.synchronizedCollection
method creates thread-safe
wrappers around collections. It returns a synchronized (thread-safe) collection
backed by the specified collection. This is essential for multi-threaded
environments.
All access to the returned collection must be through the synchronized wrapper. The wrapper ensures that all method calls are atomic. However, manual synchronization is still needed for compound operations.
Collections.synchronizedCollection Overview
The synchronizedCollection
method is part of Java's Collections
utility class. It provides basic thread safety for collection operations. The
method wraps any Collection implementation with synchronization.
The returned collection serializes all method access. This prevents concurrent modification issues. However, iteration requires explicit synchronization to avoid ConcurrentModificationException.
Basic Synchronized Collection
This example demonstrates creating a basic synchronized collection. We wrap an
ArrayList with Collections.synchronizedCollection
. The example
shows basic operations on the synchronized collection.
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()); } }
This code creates a synchronized wrapper around an ArrayList. All operations on
syncCollection
are thread-safe. The example demonstrates adding,
removing, and checking size of elements.
The output shows the collection's state after each operation. The synchronized wrapper ensures these operations are atomic when accessed from multiple threads.
Multi-threaded Access
This example shows how multiple threads can safely access a synchronized collection. We create several threads that concurrently modify the collection. The synchronized wrapper prevents data corruption.
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()); } }
This example demonstrates thread-safe collection access. Five threads each add 100 elements to the collection concurrently. The synchronized wrapper ensures all additions are properly serialized.
The final count should be exactly 500 elements (5 threads × 100 elements each). Without synchronization, the count would be unpredictable due to race conditions.
Iterating with Synchronization
Iterating over a synchronized collection requires explicit synchronization. This example shows the correct way to iterate while maintaining thread safety. The collection is locked during the entire iteration.
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); } }
This example demonstrates two approaches to iteration. The first uses explicit synchronization to prevent concurrent modification during iteration. The second uses the forEach method which is internally synchronized.
Both approaches are thread-safe, but the synchronized block provides more control. The forEach method is more concise but may be less flexible for complex operations.
Compound Operations
Compound operations on synchronized collections require additional synchronization. This example demonstrates checking and then adding an element atomically. The entire operation must be synchronized to be thread-safe.
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); } }
This example shows a common pattern: check-then-act. The contains check and subsequent add must be atomic to prevent race conditions. The synchronized block ensures no other thread can modify the collection between these operations.
Without this synchronization, another thread could add "Blue" between our check and add. This would result in duplicate elements or other inconsistencies.
Synchronized Collection vs Concurrent Collections
This example compares synchronized collections with concurrent collections like CopyOnWriteArrayList. It demonstrates performance differences and use cases for each approach.
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"); } }
This example benchmarks synchronized collections against concurrent collections. Synchronized collections use coarse-grained locking, while concurrent collections use more sophisticated techniques. The performance characteristics differ based on use case.
Synchronized collections are generally better for write-heavy workloads with simple operations. Concurrent collections often perform better for read-heavy workloads or complex operations.
Synchronized Collection with Custom Objects
This example demonstrates using synchronized collections with custom objects. It shows how to ensure thread safety when the collection elements themselves might be accessed from multiple threads.
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(); } } }
This example shows a collection of custom Product objects. The Product class has its own synchronization for price updates. The collection wrapper ensures thread-safe access to the collection structure.
Note that synchronizing the collection doesn't automatically synchronize access to the elements. The Product class must handle its own synchronization for thread-safe element access.
Synchronized Collection Performance Considerations
This example demonstrates performance implications of synchronized collections. It shows how synchronization overhead affects operations in single-threaded and multi-threaded scenarios.
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"); } }
This example benchmarks synchronized collection performance. It compares single-threaded operations between synchronized and unsynchronized collections. It also measures multi-threaded performance with the synchronized collection.
The results show that synchronization adds overhead in single-threaded scenarios. However, in multi-threaded environments, the synchronization prevents data corruption and ensures thread safety at the cost of some performance.
Source
Java Collections.synchronizedCollection Documentation
In this article, we've explored Java's Collections.synchronizedCollection
method in depth. We've covered basic usage, multi-threaded access, iteration,
compound operations, and performance considerations. Understanding these concepts
is crucial for developing thread-safe applications.
Author
List all Java tutorials.