Understanding Synchronized and Concurrent Containers in Java

The article explains Java's synchronized containers such as Vector, Stack, and Hashtable, demonstrates their usage and pitfalls, then introduces concurrent containers like ConcurrentHashMap, CopyOnWriteArrayList, and ConcurrentSkipListSet, providing code examples and best practices for thread‑safe collection handling.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Understanding Synchronized and Concurrent Containers in Java

1. Synchronized Containers

Java’s collection framework includes four main interfaces: List, Set, Queue, and Map. Many implementations (e.g., ArrayList, LinkedList, HashMap) are not thread‑safe, so Java provides synchronized containers.

Two categories of synchronized containers are:

Vector, Stack, Hashtable

Containers created via Collections.synchronizedXxx static factory methods Vector implements List and synchronizes all its methods. Stack extends Vector and is also synchronized. Hashtable implements Map with synchronized methods, unlike HashMap.

The Collections utility class offers methods such as Collections.synchronizedList, Collections.synchronizedSet, etc., which wrap ordinary collections with synchronized wrappers.

Collections.synchronizedList(List<T> list)

Example of using a synchronized Vector:

import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
public class CollectionsExample1 {
    public static int clientTotal = 5000;
    public static int threadTotal = 200;
    private static List<Integer> list = Collections.synchronizedList(Lists.newArrayList());
    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", list.size());
    }
    private static void update(int i) {
        list.add(i);
    }
}

Example of using a synchronized Hashtable:

import lombok.extern.slf4j.Slf4j;
import java.util.Hashtable;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
public class HashTableExample {
    public static int clientTotal = 5000;
    public static int threadTotal = 200;
    private static Map<Integer, Integer> map = new Hashtable<>();
    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", map.size());
    }
    private static void update(int i) {
        map.put(i, i);
    }
}

Improper use of synchronized containers can still cause issues, e.g., iterating and removing elements from a Vector concurrently may throw ArrayIndexOutOfBoundsException. Composite operations on synchronized collections are not automatically atomic; explicit locking is required.

2. Concurrent Containers

Concurrent containers, introduced in java.util.concurrent (JDK 5), provide higher scalability than synchronized containers. They include ConcurrentHashMap, CopyOnWriteArrayList, CopyOnWriteArraySet, ConcurrentSkipListSet, and ConcurrentSkipListMap. CopyOnWriteArrayList creates a new copy on each write, offering thread‑safe reads with eventual consistency. Its drawbacks are higher memory usage and lack of real‑time consistency.

import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
public class CopyOnWriteArrayListExample {
    public static int clientTotal = 5000;
    public static int threadTotal = 200;
    private static List<Integer> list = new CopyOnWriteArrayList<>();
    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", list.size());
    }
    private static void update(int i) {
        list.add(i);
    }
}
CopyOnWriteArraySet

works similarly for sets, while ConcurrentSkipListSet provides a sorted set backed by a skip‑list, supporting high concurrency.

import lombok.extern.slf4j.Slf4j;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
public class CopyOnWriteArraySetExample {
    public static int clientTotal = 5000;
    public static int threadTotal = 200;
    private static Set<Integer> set = new CopyOnWriteArraySet<>();
    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", set.size());
    }
    private static void update(int i) {
        set.add(i);
    }
}
ConcurrentSkipListSet

and ConcurrentSkipListMap use skip‑list structures to maintain sorted order with fine‑grained locking, offering near‑constant‑time operations regardless of thread count.

import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
public class ConcurrentSkipListSetExample {
    public static int clientTotal = 5000;
    public static int threadTotal = 200;
    private static ConcurrentSkipListSet<Integer> set = new ConcurrentSkipListSet<>();
    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", set.size());
    }
    private static void update(int i) {
        set.add(i);
    }
}

Similarly, ConcurrentHashMap provides a segmented lock mechanism that allows many threads to read and write concurrently, making it a frequent interview topic alongside HashMap.

import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
public class ConcurrentHashMapExample {
    public static int clientTotal = 5000;
    public static int threadTotal = 200;
    private static ConcurrentHashMap<Integer, Integer> map = new ConcurrentHashMap<>();
    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", map.size());
    }
    private static void update(int i) {
        map.put(i, i);
    }
}

Finally, the article summarizes safe sharing strategies: thread‑restricted objects, shared‑read‑only objects, thread‑safe objects (with internal synchronization), and guarded objects that require explicit locks.

Thank you for reading; hope it helps :) Source: chenxiao.blog.csdn.net/article/details/102831617
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javathread safetyCollections FrameworkConcurrent ContainersSynchronized Collections
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.