Vector, Hashtable and synchronizedMap

Collections · lesson 33 of 42 · 4 min read

Understand why locking every method is not thread safety, and what to use instead.

Open this lesson in the learning hub

Key points

  • Vector, Hashtable and Stack lock every method. They still work, but nothing new should use them.
  • Collections.synchronizedMap(m) wraps a map so each call takes one lock — every thread queues behind it.
  • Per-method locking does not make a sequence safe: get then put is still a lost update.
  • To fix it yourself you must hold the lock across the pair: synchronized (map) { ... }.
  • Iterating a synchronized wrapper needs that same manual block around the entire loop.
  • ConcurrentHashMap locks one bin and offers atomic putIfAbsent, merge and compute.

Example

import java.util.*;
import java.util.concurrent.*;

public class Main {

    static final int THREADS = 4;
    static final int LOOPS = 5_000;

    public static void main(String[] args) throws Exception {
        Map<String, Integer> wrapped = Collections.synchronizedMap(new HashMap<>());
        Map<String, Integer> guarded = Collections.synchronizedMap(new HashMap<>());
        Map<String, Integer> concurrent = new ConcurrentHashMap<>();

        ExecutorService pool = Executors.newFixedThreadPool(THREADS);
        for (int t = 0; t < THREADS; t++) {
            pool.submit(() -> {
                for (int i = 0; i < LOOPS; i++) {
                    Integer old = wrapped.get("hits");        // atomic call
                    wrapped.put("hits", old == null ? 1 : old + 1);   // atomic call, racy pair

                    synchronized (guarded) {                  // the whole pair is locked
                        Integer g = guarded.get("hits");
                        guarded.put("hits", g == null ? 1 : g + 1);
                    }
                    concurrent.merge("hits", 1, Integer::sum); // atomic on its own
                }
            });
        }
        pool.shutdown();
        pool.awaitTermination(10, TimeUnit.SECONDS);

        System.out.println("expected               : " + (THREADS * LOOPS));
        System.out.println("synchronizedMap get+put: " + wrapped.get("hits") + "   (usually short)");
        System.out.println("synchronized (map) { } : " + guarded.get("hits") + "   (you locked the pair)");
        System.out.println("ConcurrentHashMap merge: " + concurrent.get("hits") + "   (atomic per key)");

        System.out.println();
        System.out.println("Vector / Hashtable     : one lock for the whole object, on every method");
        System.out.println("legacy Stack           : extends Vector - use ArrayDeque instead");
        System.out.println("iterating a wrapper    : needs synchronized (map) { for (...) } yourself");
    }
}

Thread safety is a property of your operation, not of the collection you called it on.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Collections course, and every lesson in it is listed on the Collections contents page.