google-site-verification: google61fe8ba583a51912.html
CopyOnWriteArrayList for Read-Heavy Scenarios in Java

Introduction to CopyOnWriteArrayList

CopyOnWriteArrayList is a thread-safe variant of ArrayList where all mutative operations (add, set, remove, etc.) create a new copy of the underlying array. This makes it ideal for read-heavy scenarios where traversal operations vastly outnumber mutations.


1. Basic Usage and Characteristics

Basic Operations Example

import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; public class CopyOnWriteArrayListBasic { public static void main(String[] args) { // Create a CopyOnWriteArrayList CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); // Add elements - creates a new underlying array list.add("Apple"); list.add("Banana"); list.add("Cherry"); System.out.println("Initial list: " + list); // Iteration - uses the snapshot of the array System.out.println("\nIterating (snapshot):"); for (String fruit : list) { System.out.println("Fruit: " + fruit); // Modifications during iteration don't affect the current iteration if (fruit.equals("Banana")) { list.add("Date"); // This won't appear in current iteration } } System.out.println("List after iteration: " + list); // Concurrent modification example demonstrateConcurrentBehavior(); } private static void demonstrateConcurrentBehavior() { System.out.println("\n=== Concurrent Behavior Demo ==="); CopyOnWriteArrayList<Integer> numbers = new CopyOnWriteArrayList<>(); for (int i = 1; i <= 5; i++) { numbers.add(i); } // Reader thread Thread reader = new Thread(() -> { System.out.println("Reader starting iteration..."); for (Integer num : numbers) { System.out.println("Reader sees: " + num); try { Thread.sleep(100); // Slow reader } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } System.out.println("Reader finished iteration"); }); // Writer thread Thread writer = new Thread(() -> { try { Thread.sleep(150); // Let reader start first System.out.println("Writer adding 100..."); numbers.add(100); System.out.println("Writer added 100"); Thread.sleep(200); System.out.println("Writer removing element at index 0..."); numbers.remove(0); System.out.println("Writer removed first element"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); reader.start(); writer.start(); try { reader.join(); writer.join(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("Final list: " + numbers); } }

Performance Characteristics

import java.util.*; import java.util.concurrent.*; public class COWListCharacteristics { public static void main(String[] args) throws Exception { System.out.println("=== CopyOnWriteArrayList Characteristics ===\n"); // Memory overhead demonstration CopyOnWriteArrayList<Integer> cowList = new CopyOnWriteArrayList<>(); ArrayList<Integer> arrayList = new ArrayList<>(); int elementCount = 1000; // Measure memory usage long memoryBefore = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); for (int i = 0; i < elementCount; i++) { cowList.add(i); } long memoryAfter = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); System.out.printf("Memory used by CopyOnWriteArrayList with %d elements: %d bytes%n", elementCount, memoryAfter - memoryBefore); // Performance comparison for different operations performanceComparison(); } private static void performanceComparison() throws Exception { final int OPERATIONS = 10000; CopyOnWriteArrayList<Integer> cowList = new CopyOnWriteArrayList<>(); Vector<Integer> vector = new Vector<>(); List<Integer> syncList = Collections.synchronizedList(new ArrayList<>()); System.out.println("\n=== Read Performance (10,000 iterations) ==="); // Initialize lists for (int i = 0; i < 1000; i++) { cowList.add(i); vector.add(i); syncList.add(i); } // Read performance long cowReadTime = measureReadPerformance(cowList, OPERATIONS); long vectorReadTime = measureReadPerformance(vector, OPERATIONS); long syncListReadTime = measureReadPerformance(syncList, OPERATIONS); System.out.printf("CopyOnWriteArrayList read time: %d ms%n", cowReadTime); System.out.printf("Vector read time: %d ms%n", vectorReadTime); System.out.printf("SynchronizedList read time: %d ms%n", syncListReadTime); System.out.println("\n=== Write Performance (1,000 additions) ==="); // Write performance long cowWriteTime = measureWritePerformance(new CopyOnWriteArrayList<>(), 1000); long vectorWriteTime = measureWritePerformance(new Vector<>(), 1000); long syncListWriteTime = measureWritePerformance( Collections.synchronizedList(new ArrayList<>()), 1000); System.out.printf("CopyOnWriteArrayList write time: %d ms%n", cowWriteTime); System.out.printf("Vector write time: %d ms%n", vectorWriteTime); System.out.printf("SynchronizedList write time: %d ms%n", syncListWriteTime); } private static long measureReadPerformance(List<Integer> list, int operations) { long startTime = System.currentTimeMillis(); for (int i = 0; i < operations; i++) { for (int j = 0; j < Math.min(100, list.size()); j++) { list.get(j); // Read operation } } return System.currentTimeMillis() - startTime; } private static long measureWritePerformance(List<Integer> list, int operations) { long startTime = System.currentTimeMillis(); for (int i = 0; i < operations; i++) { list.add(i); } return System.currentTimeMillis() - startTime; } }

2. Read-Heavy Use Cases

Configuration Management System

import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; public class ConfigurationManager { private final CopyOnWriteArrayList<ConfigurationListener> listeners = new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList<ConfigEntry> configEntries = new CopyOnWriteArrayList<>(); private final AtomicLong version = new AtomicLong(0); public static class ConfigEntry { private final String key; private volatile String value; private final long lastModified; public ConfigEntry(String key, String value) { this.key = key; this.value = value; this.lastModified = System.currentTimeMillis(); } // Getters public String getKey() { return key; } public String getValue() { return value; } public long getLastModified() { return lastModified; } public void setValue(String value) { this.value = value; } @Override public String toString() { return key + "=" + value; } } public interface ConfigurationListener { void onConfigChanged(String key, String oldValue, String newValue); } // Read-heavy operations public String getConfigValue(String key) { // Fast read without locking for (ConfigEntry entry : configEntries) { if (entry.getKey().equals(key)) { return entry.getValue(); } } return null; } public List<ConfigEntry> getAllConfigs() { // Returns a snapshot - safe for iteration return new ArrayList<>(configEntries); } public boolean configExists(String key) { // Fast existence check for (ConfigEntry entry : configEntries) { if (entry.getKey().equals(key)) { return true; } } return false; } public void forEachConfig(Consumer<ConfigEntry> action) { // Safe iteration even if modifications occur for (ConfigEntry entry : configEntries) { action.accept(entry); } } // Write operations (infrequent) public void setConfigValue(String key, String value) { // Find existing entry ConfigEntry existingEntry = null; for (ConfigEntry entry : configEntries) { if (entry.getKey().equals(key)) { existingEntry = entry; break; } } if (existingEntry != null) { // Update existing - requires creating new array String oldValue = existingEntry.getValue(); CopyOnWriteArrayList<ConfigEntry> newEntries = new CopyOnWriteArrayList<>(configEntries); for (ConfigEntry entry : newEntries) { if (entry.getKey().equals(key)) { entry.setValue(value); break; } } configEntries = newEntries; notifyListeners(key, oldValue, value); } else { // Add new entry ConfigEntry newEntry = new ConfigEntry(key, value); configEntries.add(newEntry); // Creates new array internally notifyListeners(key, null, value); } version.incrementAndGet(); } public void addListener(ConfigurationListener listener) { listeners.add(listener); } public void removeListener(ConfigurationListener listener) { listeners.remove(listener); } private void notifyListeners(String key, String oldValue, String newValue) { // Safe iteration over listeners for (ConfigurationListener listener : listeners) { try { listener.onConfigChanged(key, oldValue, newValue); } catch (Exception e) { System.err.println("Error notifying listener: " + e.getMessage()); } } } // Bulk read operations public Map<String, String> getConfigAsMap() { Map<String, String> configMap = new HashMap<>(); for (ConfigEntry entry : configEntries) { configMap.put(entry.getKey(), entry.getValue()); } return configMap; } public long getVersion() { return version.get(); } // Usage example public static void main(String[] args) throws Exception { ConfigurationManager configManager = new ConfigurationManager(); // Add some initial configuration configManager.setConfigValue("database.url", "jdbc:mysql://localhost:3306/mydb"); configManager.setConfigValue("cache.enabled", "true"); configManager.setConfigValue("thread.pool.size", "10"); // Add listener configManager.addListener((key, oldValue, newValue) -> { System.out.printf("Config changed: %s from %s to %s%n", key, oldValue, newValue); }); // Simulate multiple readers ExecutorService readerPool = Executors.newFixedThreadPool(10); AtomicInteger readCount = new AtomicInteger(0); for (int i = 0; i < 1000; i++) { readerPool.submit(() -> { // Various read operations String dbUrl = configManager.getConfigValue("database.url"); boolean exists = configManager.configExists("cache.enabled"); Map<String, String> allConfigs = configManager.getConfigAsMap(); readCount.incrementAndGet(); // Simulate some processing try { Thread.sleep(1); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } // Simulate occasional writers ExecutorService writerPool = Executors.newSingleThreadExecutor(); writerPool.submit(() -> { try { Thread.sleep(100); configManager.setConfigValue("cache.enabled", "false"); Thread.sleep(200); configManager.setConfigValue("new.setting", "value"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); // Wait for completion readerPool.shutdown(); writerPool.shutdown(); readerPool.awaitTermination(5, TimeUnit.SECONDS); writerPool.awaitTermination(5, TimeUnit.SECONDS); System.out.printf("Completed %d read operations%n", readCount.get()); System.out.println("Final config version: " + configManager.getVersion()); System.out.println("All configs: " + configManager.getConfigAsMap()); } // Volatile field for thread-safe publication private volatile CopyOnWriteArrayList<ConfigEntry> configEntries = new CopyOnWriteArrayList<>(); }

Event Bus System

import java.util.*; import java.util.concurrent.*; import java.util.function.*; public class EventBus { private final CopyOnWriteArrayList<EventHandler<?>> handlers = new CopyOnWriteArrayList<>(); private final ExecutorService executor; public EventBus() { this.executor = Executors.newCachedThreadPool(); } public EventBus(ExecutorService executor) { this.executor = executor; } @SuppressWarnings("unchecked") private static class EventHandler<T> { private final Class<T> eventType; private final Consumer<T> handler; private final boolean async; public EventHandler(Class<T> eventType, Consumer<T> handler, boolean async) { this.eventType = eventType; this.handler = handler; this.async = async; } public boolean handles(Class<?> eventClass) { return eventType.isAssignableFrom(eventClass); } public void handle(Object event) { if (async) { executor.submit(() -> handler.accept((T) event)); } else { handler.accept((T) event); } } } // Read-heavy: checking if there are handlers for an event type public <T> boolean hasHandlersFor(Class<T> eventType) { for (EventHandler<?> handler : handlers) { if (handler.handles(eventType)) { return true; } } return false; } // Read-heavy: get all handler types public Set<Class<?>> getRegisteredEventTypes() { Set<Class<?>> types = new HashSet<>(); for (EventHandler<?> handler : handlers) { types.add(handler.eventType); } return types; } // Read-heavy: count handlers for specific event type public <T> int getHandlerCount(Class<T> eventType) { int count = 0; for (EventHandler<?> handler : handlers) { if (handler.handles(eventType)) { count++; } } return count; } // Write operation (infrequent) - register handler public <T> void registerHandler(Class<T> eventType, Consumer<T> handler) { registerHandler(eventType, handler, false); } public <T> void registerAsyncHandler(Class<T> eventType, Consumer<T> handler) { registerHandler(eventType, handler, true); } private <T> void registerHandler(Class<T> eventType, Consumer<T> handler, boolean async) { EventHandler<T> eventHandler = new EventHandler<>(eventType, handler, async); handlers.add(eventHandler); } // Write operation (infrequent) - unregister handler public <T> void unregisterHandler(Class<T> eventType, Consumer<T> handler) { handlers.removeIf(h -> h.eventType.equals(eventType) && h.handler == handler ); } // Read-heavy: publish event to all interested handlers public void publish(Object event) { Class<?> eventClass = event.getClass(); int handlerCount = 0; // Iteration uses snapshot - safe even if handlers are added/removed during publish for (EventHandler<?> handler : handlers) { if (handler.handles(eventClass)) { handler.handle(event); handlerCount++; } } if (handlerCount == 0) { System.out.println("No handlers for event: " + event); } } // Bulk operation: publish to multiple events public void publishAll(Collection<?> events) { for (Object event : events) { publish(event); } } public void shutdown() { executor.shutdown(); } // Demo events static class UserLoginEvent { final String username; final long timestamp; public UserLoginEvent(String username) { this.username = username; this.timestamp = System.currentTimeMillis(); } @Override public String toString() { return "UserLoginEvent{username='" + username + "', timestamp=" + timestamp + "}"; } } static class OrderCreatedEvent { final String orderId; final double amount; public OrderCreatedEvent(String orderId, double amount) { this.orderId = orderId; this.amount = amount; } @Override public String toString() { return "OrderCreatedEvent{orderId='" + orderId + "', amount=" + amount + "}"; } } static class SystemAlertEvent { final String message; final String level; public SystemAlertEvent(String message, String level) { this.message = message; this.level = level; } @Override public String toString() { return "SystemAlertEvent{message='" + message + "', level='" + level + "'}"; } } public static void main(String[] args) throws Exception { EventBus eventBus = new EventBus(); // Register handlers (infrequent writes) eventBus.registerHandler(UserLoginEvent.class, event -> { System.out.println("[LoginLogger] " + event); }); eventBus.registerAsyncHandler(UserLoginEvent.class, event -> { // Simulate slow processing try { Thread.sleep(100); System.out.println("[LoginAnalytics] Processing: " + event); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); eventBus.registerHandler(OrderCreatedEvent.class, event -> { System.out.println("[OrderProcessor] " + event); }); eventBus.registerHandler(SystemAlertEvent.class, event -> { System.out.println("[AlertSystem] " + event); }); // Simulate read-heavy usage pattern ExecutorService publisherPool = Executors.newFixedThreadPool(5); AtomicInteger eventsPublished = new AtomicInteger(0); // Multiple threads publishing events (reading handler list frequently) for (int i = 0; i < 10; i++) { final int threadId = i; publisherPool.submit(() -> { for (int j = 0; j < 100; j++) { // Check if there are handlers before publishing (read operation) if (eventBus.hasHandlersFor(UserLoginEvent.class)) { eventBus.publish(new UserLoginEvent("user-" + threadId + "-" + j)); eventsPublished.incrementAndGet(); } if (j % 10 == 0 && eventBus.hasHandlersFor(OrderCreatedEvent.class)) { eventBus.publish(new OrderCreatedEvent("order-" + j, j * 10.0)); eventsPublished.incrementAndGet(); } // Simulate some work try { Thread.sleep(1); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); } // Occasionally add new handlers (write operations) ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); scheduler.schedule(() -> { System.out.println("Adding new alert handler dynamically"); eventBus.registerHandler(SystemAlertEvent.class, event -> { System.out.println("[NewAlertHandler] " + event); }); // Publish some alerts eventBus.publish(new SystemAlertEvent("System load high", "WARNING")); }, 1, TimeUnit.SECONDS); // Let it run for a while Thread.sleep(3000); // Statistics System.out.println("\n=== Event Bus Statistics ==="); System.out.println("Total events published: " + eventsPublished.get()); System.out.println("Registered event types: " + eventBus.getRegisteredEventTypes()); System.out.println("UserLoginEvent handlers: " + eventBus.getHandlerCount(UserLoginEvent.class)); publisherPool.shutdown(); scheduler.shutdown(); eventBus.shutdown(); publisherPool.awaitTermination(5, TimeUnit.SECONDS); System.out.println("Event bus demo completed"); } }

3. Advanced Patterns and Best Practices

Snapshot-Based Reporting System

import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; import java.util.stream.*; public class SnapshotReportingSystem { private final CopyOnWriteArrayList<Transaction> transactions = new CopyOnWriteArrayList<>(); private final AtomicLong totalProcessed = new AtomicLong(0); public static class Transaction { private final String id; private final double amount; private final long timestamp; private final String type; public Transaction(String id, double amount, String type) { this.id = id; this.amount = amount; this.type = type; this.timestamp = System.currentTimeMillis(); } // Getters public String getId() { return id; } public double getAmount() { return amount; } public long getTimestamp() { return timestamp; } public String getType() { return type; } @Override public String toString() { return String.format("Transaction{id=%s, amount=%.2f, type=%s}", id, amount, type); } } // Write operation - add transaction public void addTransaction(Transaction transaction) { transactions.add(transaction); totalProcessed.incrementAndGet(); } // Batch write operation public void addTransactions(Collection<Transaction> newTransactions) { transactions.addAll(newTransactions); totalProcessed.addAndGet(newTransactions.size()); } // Read operations - all use snapshot semantics public Report generateCurrentReport() { // Snapshot is taken at the beginning of iteration List<Transaction> snapshot = new ArrayList<>(transactions); double totalAmount = snapshot.stream() .mapToDouble(Transaction::getAmount) .sum(); double averageAmount = snapshot.stream() .mapToDouble(Transaction::getAmount) .average() .orElse(0.0); Map<String, Long> countByType = snapshot.stream() .collect(Collectors.groupingBy( Transaction::getType, Collectors.counting() )); Map<String, Double> amountByType = snapshot.stream() .collect(Collectors.groupingBy( Transaction::getType, Collectors.summingDouble(Transaction::getAmount) )); return new Report(snapshot.size(), totalAmount, averageAmount, countByType, amountByType, System.currentTimeMillis()); } public List<Transaction> getTransactionsByType(String type) { return transactions.stream() .filter(t -> t.getType().equals(type)) .collect(Collectors.toList()); } public List<Transaction> getRecentTransactions(long sinceTimestamp) { return transactions.stream() .filter(t -> t.getTimestamp() >= sinceTimestamp) .collect(Collectors.toList()); } public Optional<Transaction> findTransaction(String id) { return transactions.stream() .filter(t -> t.getId().equals(id)) .findFirst(); } public boolean containsTransaction(String id) { return transactions.stream() .anyMatch(t -> t.getId().equals(id)); } public long getTotalProcessedCount() { return totalProcessed.get(); } public int getCurrentTransactionCount() { return transactions.size(); } public static class Report { private final int transactionCount; private final double totalAmount; private final double averageAmount; private final Map<String, Long> countByType; private final Map<String, Double> amountByType; private final long generatedAt; public Report(int transactionCount, double totalAmount, double averageAmount, Map<String, Long> countByType, Map<String, Double> amountByType, long generatedAt) { this.transactionCount = transactionCount; this.totalAmount = totalAmount; this.averageAmount = averageAmount; this.countByType = countByType; this.amountByType = amountByType; this.generatedAt = generatedAt; } @Override public String toString() { return String.format( "Report{count=%d, total=%.2f, avg=%.2f, types=%s, generated=%d}", transactionCount, totalAmount, averageAmount, countByType, generatedAt ); } } // Demo with concurrent access pattern public static void main(String[] args) throws Exception { SnapshotReportingSystem system = new SnapshotReportingSystem(); // Simulate transaction producers (writers) ExecutorService producerPool = Executors.newFixedThreadPool(3); ScheduledExecutorService producerScheduler = Executors.newScheduledThreadPool(1); // Generate transactions continuously but slowly producerScheduler.scheduleAtFixedRate(() -> { String[] types = {"PAYMENT", "REFUND", "TRANSFER", "FEE"}; Random random = new Random(); for (int i = 0; i < 5; i++) { String type = types[random.nextInt(types.length)]; double amount = 10 + random.nextDouble() * 1000; String id = "txn-" + System.currentTimeMillis() + "-" + i; system.addTransaction(new Transaction(id, amount, type)); } }, 0, 100, TimeUnit.MILLISECONDS); // 10 transactions per 100ms // Simulate report consumers (readers) - much more frequent ExecutorService consumerPool = Executors.newFixedThreadPool(10); AtomicInteger reportsGenerated = new AtomicInteger(0); for (int i = 0; i < 10; i++) { consumerPool.submit(() -> { while (!Thread.currentThread().isInterrupted()) { try { // Generate various types of reports (read operations) Report currentReport = system.generateCurrentReport(); reportsGenerated.incrementAndGet(); // Occasionally query specific data if (Math.random() < 0.1) { List<Transaction> payments = system.getTransactionsByType("PAYMENT"); System.out.printf("Found %d PAYMENT transactions%n", payments.size()); } if (Math.random() < 0.05) { long recentTime = System.currentTimeMillis() - 5000; List<Transaction> recent = system.getRecentTransactions(recentTime); System.out.printf("Found %d recent transactions%n", recent.size()); } Thread.sleep(10); // Readers are very fast } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); } // Run for a while Thread.sleep(5000); // Shutdown and statistics producerScheduler.shutdown(); consumerPool.shutdownNow(); producerScheduler.awaitTermination(1, TimeUnit.SECONDS); consumerPool.awaitTermination(1, TimeUnit.SECONDS); System.out.println("\n=== Snapshot Reporting System Statistics ==="); System.out.println("Total transactions processed: " + system.getTotalProcessedCount()); System.out.println("Current transactions in system: " + system.getCurrentTransactionCount()); System.out.println("Reports generated: " + reportsGenerated.get()); // Final report Report finalReport = system.generateCurrentReport(); System.out.println("Final report: " + finalReport); // Demonstrate snapshot consistency System.out.println("\n=== Snapshot Consistency Demo ==="); List<Transaction> snapshot1 = new ArrayList<>(system.transactions); System.out.println("Snapshot 1 size: " + snapshot1.size()); // Add some transactions system.addTransaction(new Transaction("new-1", 100, "PAYMENT")); system.addTransaction(new Transaction("new-2", 200, "REFUND")); List<Transaction> snapshot2 = new ArrayList<>(system.transactions); System.out.println("Snapshot 2 size: " + snapshot2.size()); System.out.println("Snapshot 1 unchanged: " + snapshot1.size()); } }

Memory-Efficient Patterns

import java.util.*; import java.util.concurrent.*; import java.util.function.*; public class MemoryEfficientCOWPatterns { // Pattern 1: Lazy snapshot creation public static class LazySnapshotList<T> { private final CopyOnWriteArrayList<T> delegate = new CopyOnWriteArrayList<>(); private volatile List<T> lastSnapshot; private volatile long lastSnapshotTime; private final long snapshotTTL; public LazySnapshotList(long snapshotTTL) { this.snapshotTTL = snapshotTTL; } public List<T> getSnapshot() { long currentTime = System.currentTimeMillis(); List<T> snapshot = lastSnapshot; // Return cached snapshot if it's still fresh if (snapshot != null && (currentTime - lastSnapshotTime) < snapshotTTL) { return snapshot; } // Create new snapshot snapshot = new ArrayList<>(delegate); lastSnapshot = snapshot; lastSnapshotTime = currentTime; return snapshot; } public void add(T element) { delegate.add(element); // Invalidate cached snapshot on write lastSnapshot = null; } public boolean remove(T element) { boolean removed = delegate.remove(element); if (removed) { lastSnapshot = null; } return removed; } } // Pattern 2: Batched writes to reduce copy overhead public static class BatchedWriteList<T> { private final CopyOnWriteArrayList<T> delegate = new CopyOnWriteArrayList<>(); private final List<T> writeBuffer = new ArrayList<>(); private final int batchSize; private final Object writeLock = new Object(); public BatchedWriteList(int batchSize) { this.batchSize = batchSize; } public void add(T element) { synchronized (writeLock) { writeBuffer.add(element); if (writeBuffer.size() >= batchSize) { flushBuffer(); } } } public void flush() { synchronized (writeLock) { if (!writeBuffer.isEmpty()) { flushBuffer(); } } } private void flushBuffer() { delegate.addAll(writeBuffer); writeBuffer.clear(); } // Read operations delegate directly public boolean contains(T element) { return delegate.contains(element) || writeBuffer.contains(element); } public int size() { return delegate.size() + writeBuffer.size(); } public List<T> getSnapshot() { flush(); // Ensure all writes are included return new ArrayList<>(delegate); } } // Pattern 3: Size-limited COW list with eviction public static class SizeBoundedCOWList<T> { private final CopyOnWriteArrayList<T> delegate = new CopyOnWriteArrayList<>(); private final int maxSize; private final Consumer<T> evictionHandler; public SizeBoundedCOWList(int maxSize) { this(maxSize, null); } public SizeBoundedCOWList(int maxSize, Consumer<T> evictionHandler) { this.maxSize = maxSize; this.evictionHandler = evictionHandler; } public void add(T element) { delegate.add(element); // Check if we need to evict while (delegate.size() > maxSize) { T removed = delegate.remove(0); if (evictionHandler != null) { evictionHandler.accept(removed); } } } // Delegate read operations public boolean contains(T element) { return delegate.contains(element); } public int size() { return delegate.size(); } public List<T> getSnapshot() { return new ArrayList<>(delegate); } } // Demo and benchmarking public static void main(String[] args) throws Exception { System.out.println("=== Memory-Efficient Patterns Demo ===\n"); // Test LazySnapshotList LazySnapshotList<String> lazyList = new LazySnapshotList<>(1000); // 1 second TTL testLazySnapshotPattern(lazyList); // Test BatchedWriteList BatchedWriteList<Integer> batchedList = new BatchedWriteList<>(10); testBatchedWritePattern(batchedList); // Test SizeBoundedCOWList SizeBoundedCOWList<String> boundedList = new SizeBoundedCOWList<>(5, item -> System.out.println("Evicted: " + item)); testBoundedPattern(boundedList); // Memory usage comparison compareMemoryUsage(); } private static void testLazySnapshotPattern(LazySnapshotList<String> list) throws Exception { System.out.println("=== Testing LazySnapshotList ==="); // Add some data for (int i = 0; i < 5; i++) { list.add("item-" + i); } // First snapshot - will create List<String> snapshot1 = list.getSnapshot(); System.out.println("Snapshot 1: " + snapshot1); // Immediate second snapshot - should reuse cached List<String> snapshot2 = list.getSnapshot(); System.out.println("Snapshot 2 (cached): " + snapshot2); System.out.println("Same instance: " + (snapshot1 == snapshot2)); // Wait for TTL to expire Thread.sleep(1100); // Third snapshot - should create new List<String> snapshot3 = list.getSnapshot(); System.out.println("Snapshot 3 (new): " + snapshot3); System.out.println("Different instance: " + (snapshot1 != snapshot3)); } private static void testBatchedWritePattern(BatchedWriteList<Integer> list) throws Exception { System.out.println("\n=== Testing BatchedWriteList ==="); ExecutorService executor = Executors.newFixedThreadPool(3); // Multiple writers adding data for (int i = 0; i < 3; i++) { final int writerId = i; executor.submit(() -> { for (int j = 0; j < 15; j++) { list.add(writerId * 100 + j); try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); } executor.shutdown(); executor.awaitTermination(1, TimeUnit.SECONDS); // Force flush and get snapshot list.flush(); List<Integer> snapshot = list.getSnapshot(); System.out.println("Final snapshot size: " + snapshot.size()); System.out.println("Sample data: " + snapshot.subList(0, Math.min(10, snapshot.size()))); } private static void testBoundedPattern(SizeBoundedCOWList<String> list) { System.out.println("\n=== Testing SizeBoundedCOWList ==="); for (int i = 0; i < 10; i++) { list.add("data-" + i); System.out.println("After adding data-" + i + ", size: " + list.size()); } List<String> snapshot = list.getSnapshot(); System.out.println("Final snapshot: " + snapshot); } private static void compareMemoryUsage() { System.out.println("\n=== Memory Usage Comparison ==="); int elementCount = 10000; // Regular CopyOnWriteArrayList long startMemory = getUsedMemory(); CopyOnWriteArrayList<Integer> regularList = new CopyOnWriteArrayList<>(); for (int i = 0; i < elementCount; i++) { regularList.add(i); } long regularMemory = getUsedMemory() - startMemory; // Batched CopyOnWriteArrayList startMemory = getUsedMemory(); BatchedWriteList<Integer> batchedList = new BatchedWriteList<>(100); for (int i = 0; i < elementCount; i++) { batchedList.add(i); } batchedList.flush(); long batchedMemory = getUsedMemory() - startMemory; System.out.printf("Regular COW list memory: %d bytes%n", regularMemory); System.out.printf("Batched COW list memory: %d bytes%n", batchedMemory); System.out.printf("Memory saving: %.1f%%%n", (1 - (double)batchedMemory / regularMemory) * 100); } private static long getUsedMemory() { Runtime runtime = Runtime.getRuntime(); return runtime.totalMemory() - runtime.freeMemory(); } }

Summary

Key Benefits of CopyOnWriteArrayList:

  1. Thread-Safe Reads: No locking required for read operations
  2. Snapshot Consistency: Iterators see a consistent snapshot
  3. No ConcurrentModificationException: Safe for concurrent modification during iteration
  4. Simple Usage: No explicit synchronization needed

Ideal Use Cases:

  • Configuration systems with infrequent updates
  • Event listener lists in observer patterns
  • Read-mostly caches
  • Snapshot-based reporting systems
  • Immutable data views

Performance Characteristics:

  • Read Operations: O(1) - very fast, no locking
  • Write Operations: O(n) - slow, involves array copying
  • Memory Usage: Higher due to copy-on-write semantics
  • Iteration: Fast and consistent

Best Practices:

  1. Use only for read-heavy workloads (reads >> writes)
  2. Prefer batch writes to reduce copy overhead
  3. Consider memory implications for large collections
  4. Use appropriate initial capacity if known
  5. Monitor write performance in production

When to Avoid:

  • Write-heavy scenarios
  • Memory-constrained environments
  • Large collections with frequent modifications
  • Real-time systems where write latency matters

CopyOnWriteArrayList is a specialized collection that excels in specific read-heavy, write-rarely scenarios, providing excellent read performance and thread safety at the cost of write performance and memory usage.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper