Part 01 · Java Core · 1.1.03

Java Collections: interface, implementation và cách chọn

Chọn collection từ semantics trước: có duplicate không, cần ordering hay sorted order, truy cập theo key/index, queue policy và concurrency ra sao. Complexity là bước kiểm tra tiếp theo.

Học sâu phần bên dưới: mở Collections Internals để theo call flow của ArrayList, HashMap, TreeMap, PriorityQueue, concurrent collections và làm 4 failure/performance labs.
Phạm vi phiên bản. Trang dùng Java SE 21 làm baseline API, không coi đây là tuyên bố về bản Java mới nhất. SequencedCollection, SequencedSetSequencedMap xuất hiện từ Java 21; ví dụ Java bên dưới không cần preview feature. [R04]

Các phần “Bổ sung” tách kiến thức kiểm chứng từ API với khuyến nghị thiết kế. Contract là điều API cam kết; trade-off phải được đo trên workload; scope chỉ rõ operation, object hoặc thread nào được bảo vệ.

1. Sơ đồ interface

Bản đồ các họ collection. Sơ đồ dưới giữ đủ interface và implementation của tài liệu nguồn. Đây là cách nhóm để học, không phải danh sách đầy đủ các quan hệ extends/implements; phần Java 21 ngay sau sơ đồ bổ sung các superinterface còn thiếu. [R04] [R01]

Iterable<E>
└── Collection<E>
    ├── List<E>
    │   ├── ArrayList
    │   ├── LinkedList
    │   └── CopyOnWriteArrayList
    ├── Set<E>
    │   ├── HashSet
    │   ├── LinkedHashSet
    │   ├── SortedSet / NavigableSet
    │   │   ├── TreeSet
    │   │   └── ConcurrentSkipListSet
    │   └── EnumSet
    └── Queue<E>
        ├── Deque<E>
        │   ├── ArrayDeque
        │   ├── LinkedList
        │   └── ConcurrentLinkedDeque
        ├── PriorityQueue
        └── BlockingQueue
            ├── ArrayBlockingQueue
            ├── LinkedBlockingQueue
            ├── PriorityBlockingQueue
            └── SynchronousQueue

Map<K,V> (không extends Collection)
├── HashMap / LinkedHashMap / WeakHashMap / IdentityHashMap
├── SortedMap / NavigableMap → TreeMap / ConcurrentSkipListMap
├── EnumMap
└── ConcurrentMap → ConcurrentHashMap
Điểm hay hỏi: Map thuộc Collections Framework nhưng không extends Collection, vì nó lưu mapping key–value thay vì một tập phần tử đơn.

1.1. Bổ sung · đọc contract trước tên class

Semantics quyết định họ collection
HọĐiều cần xác địnhKhông được suy ra
ListSequence có index; các implementation phổ biến cho phép duplicate. [R06]Có index không có nghĩa get(i) luôn O(1).
SetKhông lặp phần tử theo equality contract; chọn thêm yêu cầu encounter order hoặc comparator. [R10] [R13]Unique không có nghĩa sorted.
MapMỗi key tối đa một mapping; nhiều key có thể cùng value. [R02]Không kế thừa Collection; có thể thao tác qua collection views.
Queue / DequeChính sách lấy head và thao tác ở một/hai đầu. [R25] [R26]Queue không mặc định FIFO, bounded hoặc thread-safe.

1.2. Bổ sung · hierarchy có encounter order từ Java 21

Contract Java 21: ListSequencedCollection; Deque đồng thời là QueueSequencedCollection. SequencedSet nối Set với SequencedCollection, còn SequencedMap vẫn ở nhánh Map riêng. [R04] [R06] [R26]

Quan hệ is-a, đọc trái sang phải là: subtype → supertype

List                 → SequencedCollection → Collection → Iterable
Deque                → Queue               → Collection → Iterable
Deque                → SequencedCollection
SequencedSet         → Set + SequencedCollection
SortedSet            → SequencedSet
NavigableSet         → SortedSet

SequencedMap         → Map
SortedMap            → SequencedMap
NavigableMap         → SortedMap

LinkedHashSet        → SequencedSet
LinkedHashMap        → SequencedMap

Ordered là có encounter order; insertion order theo thứ tự chèn; access order thay đổi theo truy cập; sorted dựa natural order/comparator. Không đồng nhất các khái niệm này. reversed() cung cấp reverse-ordered view; mutation là optional, và sorted collections không cho tự đặt vị trí bằng addFirst/addLast. [R04] [R05] [R17]

Ví dụ chạy được · SequencedCheck.java

Quan sát reverse view của một ArrayList: đổi phần tử đầu của view làm đổi phần tử cuối list gốc. Kết quả cần thấy: PASS: reversed view.

import java.util.ArrayList;
import java.util.List;

public class SequencedCheck {
    public static void main(String[] args) {
        List<String> original = new ArrayList<>(List.of("A", "B", "C"));
        List<String> reversed = original.reversed(); // Java 21
        if (!reversed.equals(List.of("C", "B", "A"))) {
            throw new AssertionError("Unexpected encounter order");
        }
        reversed.set(0, "Z"); // View writes through to this ArrayList
        if (!original.equals(List.of("A", "B", "Z"))) {
            throw new AssertionError("Expected a backed view");
        }
        System.out.println("PASS: reversed view");
    }
}

Chạy với JDK 21: javac --release 21 SequencedCheck.java rồi java SequencedCheck.

2. List implementations

ImplementationCấu trúcĐiểm mạnhĐiểm yếu
ArrayListMảng động contiguousGet theo index O(1), append amortized O(1), locality tốt.Insert/remove giữa O(n), resize phải copy.
LinkedListDoubly-linked nodesThêm/xóa O(1) khi đã có node/iterator; implement Deque.Get/search O(n), allocation và locality kém; hiếm khi tốt hơn ArrayList.
CopyOnWriteArrayListCopy toàn array khi writeIteration snapshot, read không lock; hợp listener list ít đổi.Write O(n), memory spike; không hợp mutation thường xuyên.

2.1. Bổ sung · complexity phải gồm chi phí tìm vị trí

Làm rõ mô hình: “contiguous” trong bảng là các slot của backing array; không có cam kết rằng mọi object phần tử nằm cạnh nhau. ArrayList append có chi phí amortized O(1), không phải mọi lần append đều O(1); resize vẫn có thể copy O(n). Capacity/growth policy cụ thể không phải contract của List. [R07] [R06]

LinkedList chỉ có lợi thế nối/gỡ node O(1) sau khi iterator đã ở đúng vị trí; API không đưa raw node cho caller. Gọi listIterator(i) hoặc remove(i) còn có traversal. Vì vậy, “nhiều insert/remove” chưa đủ để chọn linked list. Kết luận “hiếm khi nhanh hơn” trong bảng là heuristic cần benchmark, không là guarantee. [R08]

Trade-off cần đo: so cùng thao tác, kích thước, vị trí insert/remove và tỉ lệ read/write; tính cả allocation, resize, GC và latency. Tách “tìm vị trí” khỏi “thực hiện sửa”. Đừng dùng một lần đo hoặc Big-O để khẳng định tốc độ tuyệt đối.

2.2. Bổ sung · CopyOnWriteArrayList không đóng băng object

Iterator giữ snapshot backing array tại lúc tạo; các write sau đó không đổi tập reference nó đang duyệt. Nhưng object được các reference trỏ tới vẫn có thể mutable. Iterator này không hỗ trợ remove; list iterator cũng không hỗ trợ set/add. [R09]

Trade-off: phù hợp listener/registry nhỏ, ít thay đổi; mỗi write có hiệu lực có thể copy array, và iterator sống lâu có thể giữ snapshot cũ. Đây không phải lựa chọn mặc định cho write-heavy workload, cũng không tự làm phần tử mutable thành thread-safe. [R09]

3. Set implementations

3.1. Bổ sung · “trùng” phải khớp business identity

Contract: hai object equals nhau phải có cùng hash code. Tránh đổi field tham gia equals/hashCode khi object còn trong hash set hoặc đang làm map key; lookup/remove có thể không còn đúng mong đợi. Với sorted set, tránh đổi field comparator đang dùng. [R10] [R02]

Hiệu chỉnh cách đọc TreeSet: compare(a,b)==0 khiến tree coi chúng là cùng phần tử. Comparator consistent với equals là điều kiện để tuân thủ general Set contract, không chỉ để kết quả “trực giác”. TreeSet được backing bởi TreeMap; red-black tree nằm trong implementation đó. [R13] [R18]

Ví dụ chạy được · SetEqualityCheck.java

BigDecimal minh họa hai định nghĩa equality: equals xét cả scale, còn compareTo so giá trị số. Kết quả: PASS: HashSet=2, TreeSet=1; đây là khác biệt semantics, không phải mất dữ liệu ngẫu nhiên. [R15]

import java.math.BigDecimal;
import java.util.HashSet;
import java.util.List;
import java.util.TreeSet;

public class SetEqualityCheck {
    public static void main(String[] args) {
        var a = new BigDecimal("1.0");
        var b = new BigDecimal("1.00");
        var input = List.of(a, b);
        int byEquals = new HashSet<>(input).size();
        int byComparison = new TreeSet<>(input).size();
        if (a.equals(b) || a.compareTo(b) != 0
                || byEquals != 2 || byComparison != 1) {
            throw new AssertionError("Unexpected equality semantics");
        }
        System.out.println("PASS: HashSet=2, TreeSet=1");
    }
}

Chạy với JDK 21: javac --release 21 SetEqualityCheck.java rồi java SetEqualityCheck.

3.2. Bổ sung · order, null và concurrent sorted set

HashSet nhận một phần tử null; O(1) trung bình giả định hash phân bố tốt. LinkedHashSet giữ insertion order thông thường, nhưng Java 21 có thao tác đặt lại vị trí rõ ràng. EnumSet duyệt theo thứ tự khai báo enum, không theo thứ tự add, và cấm null. [R11] [R12] [R14]

ConcurrentSkipListSet dành cho sorted/range access dùng chung giữa threads: expected O(log n) cho thao tác chính, weakly consistent iterator, không nhận null. Đổi từ TreeSet sang loại concurrent này không tạo ra transaction nhiều thao tác. [R24]

4. Map implementations

MapSemanticsUse case
HashMapAverage O(1), một null key, không thread-safe/order.Lookup tổng quát.
LinkedHashMapInsertion hoặc access order.Ordered map, simple bounded LRU qua removeEldestEntry.
TreeMapSorted/Navigable, O(log n).Range, floor/ceiling, ordered traversal.
EnumMapEnum keys, array-like internal representation.Mapping theo enum domain.
WeakHashMapWeak keys có thể mất khi không còn strong reference.Metadata/cache phụ thuộc lifecycle key; không phải general cache.
IdentityHashMapSo key bằng ==, không phải equals.Object graph/identity algorithms chuyên biệt.
ConcurrentHashMapConcurrent, weakly consistent iteration, không null.Shared map; compound invariant dùng atomic APIs.

4.1. Bổ sung · order, lifecycle và các map chuyên biệt

HashMap cho phép null values và một null key; get(key)==null chưa phân biệt được “không có key” với “value là null”. Iteration có chi phí theo capacity + size, nên cấp capacity quá lớn không miễn phí. [R16] [R02]

LinkedHashMap mặc định insertion order; constructor với accessOrder=true dùng access order. removeEldestEntry cho phép LRU giới hạn đơn giản, nhưng không tự bổ sung TTL hay concurrency policy. Trong access-order map, get có thể đổi encounter order và được xem là structural modification. [R17]

Caveat khi chọn map chuyên biệt
MapContract / giới hạn cần nhớ
TreeMapNatural ordering thường không nhận null key; comparator tùy chỉnh có thể hỗ trợ null. Null values được phép. Comparator phải consistent với equals để giữ general Map contract. [R18]
EnumMapKeys cùng một enum type; duyệt theo thứ tự khai báo enum; cấm null key nhưng cho phép null value. [R19]
WeakHashMapGC có thể làm entry biến mất mà ứng dụng không gọi remove; không cam kết thời điểm. Value được giữ mạnh: value trỏ lại key có thể giữ key sống, làm kỳ vọng giải phóng thất bại. Không thay thế eviction/TTL policy. [R20]
IdentityHashMapReference equality == áp dụng cho keys và values; cố ý khác general Map contract. Chỉ dùng khi domain thật sự là object identity, như duyệt graph/copy graph. [R21]
ConcurrentSkipListMapKết hợp concurrent access với sorted/range queries; expected O(log n), weak iteration, không null keys/values. Đây là nhánh bổ sung khi vừa cần concurrency vừa cần range. [R23]

4.2. Bổ sung · atomic theo key, không atomic toàn nghiệp vụ

ConcurrentHashMap cấm cả null key và null value. Dùng putIfAbsent, compute hoặc merge thay cho chuỗi check-then-act không được bảo vệ. Atomicity của những API này không biến nhiều key hoặc thao tác database thành một transaction. Callback cần ngắn, không sửa mapping khác của chính map, và không làm I/O chậm trong critical update. [R22]

Ví dụ chạy được · ConcurrentCounterCheck.java

Bốn worker cùng tăng một key bằng merge. Future.get() đợi hoàn tất và đưa lỗi worker về main. Kết quả 4000 là số increment được lập trình, không phải benchmark throughput.

import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ConcurrentCounterCheck {
    public static void main(String[] args) throws Exception {
        var counts = new ConcurrentHashMap<String, Integer>();
        var tasks = new ArrayList<Future<?>>();
        try (var pool = Executors.newFixedThreadPool(4)) {
            for (int worker = 0; worker < 4; worker++) {
                tasks.add(pool.submit(() -> {
                    for (int i = 0; i < 1_000; i++) {
                        counts.merge("events", 1, Integer::sum);
                    }
                }));
            }
            for (var task : tasks) {
                task.get(); // Wait, and propagate worker failures
            }
        }
        if (!Integer.valueOf(4_000).equals(counts.get("events"))) {
            throw new AssertionError("Lost updates");
        }
        System.out.println("PASS: per-key merge=4000");
    }
}

Chạy với JDK 21: javac --release 21 ConcurrentCounterCheck.java rồi java ConcurrentCounterCheck.

Scope guarantee: ConcurrentHashMap<K, ArrayList<V>> chỉ bảo vệ thao tác map, không bảo vệ các lần add trên cùng list value. Invariant liên quan nhiều key hoặc cả value cần thiết kế đồng bộ riêng. Kết quả size()/duyệt khi đang có write cũng không phải snapshot toàn map. [R22]

5. Queue và Deque

Queue cung cấp hai nhóm API: add/remove/element ném exception khi thất bại; offer/poll/peek trả special value. Deque thao tác hai đầu và thường thay cả queue lẫn stack; ưu tiên ArrayDeque thay Stack.

5.1. Bổ sung · chọn API khi đầy và khi rỗng

Queue khác BlockingQueue ở khả năng chờ
Thao tácExceptionTrả ngayChờ trong BlockingQueue
Thêm phần tửadd(e): capacity đầy → IllegalStateExceptionoffer(e): đầy → falseput(e); hoặc offer(e, timeout, unit)
Lấy và xóa headremove(): rỗng → NoSuchElementExceptionpoll(): rỗng → nulltake(); hoặc poll(timeout, unit)
Chỉ xem headelement(): rỗng → NoSuchElementExceptionpeek(): rỗng → nullKhông có blocking-peek chung

Không phải “offer không bao giờ ném exception”: null/type/value không hợp lệ vẫn có thể gây exception. null được dùng làm sentinel “rỗng”, nên tránh null ngay cả khi một implementation như LinkedList cho phép. [R25] [R29]

Với Deque, FIFO thường là addLast/offerLast + removeFirst/pollFirst; LIFO là push + pop ở đầu. ArrayDeque có nhiều thao tác hai đầu amortized O(1), không null và không thread-safe. ConcurrentLinkedDeque là lựa chọn concurrent unbounded, không tạo capacity backpressure. [R26] [R27] [R35]

5.2. Bổ sung · priority không phải sorted iteration

Head của PriorityQueue là phần tử nhỏ nhất theo comparator; đảo comparator để lấy giá trị lớn nhất trước. Các phần tử bằng priority không được bảo đảm FIFO. offer/poll O(log n), peek O(1); contains/remove(Object) O(n). Muốn lấy theo priority, drain bằng poll/remove, không dùng thứ tự iterator. [R28]

Blocking không đồng nghĩa bounded. PriorityBlockingQueue logically unbounded: take() có thể đợi dữ liệu, còn put() không đợi capacity. LinkedBlockingQueue() mặc định có capacity Integer.MAX_VALUE, không có nghĩa bộ nhớ thực đủ chứa ngần ấy phần tử. [R32] [R31]

SynchronousQueue không có storage capacity: offer chỉ thành công khi có consumer sẵn sàng nhận. ConcurrentLinkedQueue là unbounded FIFO, nên application phải giới hạn admission khi producer nhanh hơn consumer. ArrayBlockingQueue có thể bật fairness cho các thread đang chờ, đổi lại thường giảm throughput; fairness không phải cam kết latency tuyệt đối. [R33] [R34] [R30]

Ví dụ chạy được · QueuePolicyCheck.java

Queue capacity 2 được làm đầy khi chưa chạy consumer. Phần tử thứ ba phải bị từ chối sau timed offer; sau đó FIFO và priority-drain được kiểm tra riêng. Không kiểm chứng elapsed time đúng 10 ms vì scheduler có thể làm thời gian thực dài hơn.

import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

public class QueuePolicyCheck {
    public static void main(String[] args) throws InterruptedException {
        var inbox = new ArrayBlockingQueue<String>(2);
        if (!inbox.offer("A") || !inbox.offer("B")) {
            throw new AssertionError("Expected two accepted elements");
        }
        // No consumer is running: the queue stays full.
        if (inbox.offer("C", 10, TimeUnit.MILLISECONDS)) {
            throw new AssertionError("Expected explicit overload rejection");
        }
        if (!"A".equals(inbox.take()) || !"B".equals(inbox.take())
                || inbox.poll() != null) {
            throw new AssertionError("Unexpected FIFO / empty behavior");
        }

        var priority = new PriorityQueue<>(List.of(3, 1, 2));
        var drained = new ArrayList<Integer>();
        while (!priority.isEmpty()) {
            drained.add(priority.remove()); // Not iterator traversal
        }
        if (!drained.equals(List.of(1, 2, 3))) {
            throw new AssertionError("Unexpected priority order");
        }
        System.out.println("PASS: bounded rejection, FIFO, priority drain");
    }
}

Chạy với JDK 21: javac --release 21 QueuePolicyCheck.java rồi java QueuePolicyCheck.

5.3. Bổ sung · backpressure là policy của toàn flow

Khuyến nghị thiết kế: chọn capacity từ bộ nhớ và thời gian chờ cho phép; định nghĩa rõ overload sẽ block, timeout, reject hay drop có ghi nhận. Đo queue depth, oldest-item age, timeout/rejection rate và thời gian xử lý. Đừng busy-retry ngay khi offer trả false, vì như vậy làm mất tác dụng giới hạn tải.

Scope: enqueue thành công không có nghĩa business processing đã xong hoặc dữ liệu bền qua process crash. BlockingQueue cung cấp happens-before từ hành động trước khi put sang hành động sau khi thread khác lấy phần tử, không bảo vệ mọi mutation sau handoff. API cũng không có cơ chế close/shutdown chung; cần quy ước interruption, drain hoặc tín hiệu kết thúc. [R29]

6. Views, iteration và mutability

6.1. Bổ sung · backed view là chia sẻ state, không phải bản sao

Làm rõ “thường là” trong nguồn: Map.keySet(), values()entrySet() có backed-view contract. Với mutable map hỗ trợ thao tác đó, xóa qua view xóa mapping; không suy ra view hỗ trợ add. Unmodifiable map vẫn không cho sửa qua view. [R02]

subList(from, to) lấy khoảng [from, to). Structural modification trực tiếp ở backing list, ngoài đường view, làm semantics của sublist undefined theo contract; không chỉ đơn giản “chắc chắn ném ConcurrentModificationException”. Muốn tách vòng đời và cấu trúc, tạo copy dưới ownership/lock phù hợp. [R06]

Đừng trộn lẫn fixed-size, unmodifiable và immutable
Cách tạoMutation qua kết quảQuan hệ với dữ liệu đầu vào
Arrays.asList(array)Fixed-size: hỗ trợ set, không add/remove đổi sizeBacked bởi array; thay slot phản chiếu hai phía. [R37]
Collections.unmodifiableList(list)Không add/remove/set qua viewBacking list vẫn sửa được qua reference khác. [R36]
List.of(...)Unmodifiable; không nullKhông deep-copy object phần tử; duplicate được phép. [R06]
Set.of(...) / Map.of(...)Unmodifiable; không nullSet.of từ chối phần tử trùng; Map.of từ chối key trùng; không hứa insertion order. [R10] [R02]
List.copyOf(collection)Unmodifiable; không nullSnapshot nông về membership/order, không deep clone; implementation có thể tái sử dụng instance đủ điều kiện. [R06] [R38]
new ArrayList<>(collection)MutableCấu trúc list độc lập, nhưng vẫn chia sẻ reference tới phần tử. [R07]

Version caveat: các factory of của List/Set/Map có từ Java 9; copyOf có từ Java 10. “Unmodifiable” nói về API của container, không khẳng định element graph bất biến. Lấy copy trong khi source bị sửa đồng thời cũng không tự tạo snapshot nguyên tử của toàn state. [R06] [R10] [R02] [R38]

Ví dụ chạy được · ViewCopyCheck.java

So sánh một read-only view với shallow copy, sau đó quan sát mutable element, subList, keySet và fixed-size array view. Kết quả cần thấy: PASS: backed views and shallow copy.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

public class ViewCopyCheck {
    public static void main(String[] args) {
        var backing = new ArrayList<>(List.of("A", "B"));
        var view = Collections.unmodifiableList(backing);
        var copy = List.copyOf(backing);
        backing.set(0, "Z");
        if (!view.get(0).equals("Z") || !copy.get(0).equals("A")) {
            throw new AssertionError("View and copy must differ");
        }

        var mutableElement = new StringBuilder("A");
        var shallow = List.copyOf(List.of(mutableElement));
        mutableElement.append("!");
        if (!shallow.get(0).toString().equals("A!")) {
            throw new AssertionError("Copy is shallow, not a deep clone");
        }

        var range = backing.subList(0, 1);
        range.clear(); // Structural edit through the view is supported here
        if (!backing.equals(List.of("B"))) {
            throw new AssertionError("subList must affect the backing list");
        }

        var map = new HashMap<>(java.util.Map.of("A", 1, "B", 2));
        map.keySet().remove("A");
        if (map.containsKey("A")) {
            throw new AssertionError("keySet is backed by the map");
        }

        String[] array = {"A", "B"};
        var fixedSize = Arrays.asList(array);
        fixedSize.set(0, "Z");
        if (!array[0].equals("Z")) {
            throw new AssertionError("Arrays.asList must write through");
        }
        System.out.println("PASS: backed views and shallow copy");
    }
}

Chạy với JDK 21: javac --release 21 ViewCopyCheck.java rồi java ViewCopyCheck.

6.2. Bổ sung · ba kiểu iterator, ba giới hạn khác nhau

Iterator behavior không thay thế synchronization
KiểuVí dụQuan sát / giới hạn
Fail-fast, best effortArrayList, HashMapCó thể phát hiện structural modification sai đường và ném CME; có thể xảy ra ngay trong một thread. Không được phụ thuộc CME cho correctness. [R07] [R16]
SnapshotCopyOnWriteArrayListTập reference tại lúc tạo iterator; không thấy write sau đó, không cho iterator.remove; không deep snapshot phần tử. [R09]
Weakly consistentConcurrentHashMap, concurrent skip lists/queuesDuyệt được khi cập nhật; có thể thấy một phần update. Không CME do concurrent update, nhưng không phải ảnh chụp toàn collection ở một thời điểm. [R22] [R23] [R34]

Cách sửa an toàn có scope: với mutable list thông thường, dùng Iterator.remove() của chính iterator đang duyệt hoặc removeIf trong quyền truy cập phù hợp. Với Collections.synchronizedList, traversal vẫn phải đặt trong synchronized(wrapper), và mọi đường truy cập phải dùng cùng wrapper; không khóa một view khác hoặc lộ backing list ra ngoài. [R01] [R36]

Ví dụ chạy được · IteratorModesCheck.java

Kiểm tra xóa bằng iterator hợp lệ, snapshot của COW và weak iteration. Cố ý không assert key B có xuất hiện trong weak iterator; việc chạy ra một kết quả không biến nó thành guarantee.

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

public class IteratorModesCheck {
    public static void main(String[] args) {
        var ordinary = new ArrayList<>(List.of(1, 2, 3));
        var editable = ordinary.iterator();
        while (editable.hasNext()) {
            if (editable.next() == 2) {
                editable.remove(); // Mutate via this iterator
            }
        }
        if (!ordinary.equals(List.of(1, 3))) {
            throw new AssertionError("Iterator removal failed");
        }

        var listeners = new CopyOnWriteArrayList<>(List.of("A", "B"));
        var snapshot = listeners.iterator();
        listeners.add("C");
        var seen = new ArrayList<String>();
        snapshot.forEachRemaining(seen::add);
        if (!seen.equals(List.of("A", "B"))) {
            throw new AssertionError("Expected the old snapshot");
        }

        var shared = new ConcurrentHashMap<String, Integer>();
        shared.put("A", 1);
        var weak = shared.keySet().iterator();
        shared.put("B", 2);
        weak.forEachRemaining(key -> {
            if (!key.equals("A") && !key.equals("B")) {
                throw new AssertionError("Unknown key");
            }
        });
        // Deliberately do not assert whether B was observed.
        System.out.println("PASS: iterator removal, snapshot, weak iteration");
    }
}

Chạy với JDK 21: javac --release 21 IteratorModesCheck.java rồi java IteratorModesCheck.

7. Cách chọn nhanh

Cần key → Map
  cần sorted/range → TreeMap
  cần insertion/access order → LinkedHashMap
  shared concurrent → ConcurrentHashMap
  còn lại → HashMap

Cần collection phần tử
  cần duplicate + index → ArrayList
  cần unique → HashSet
    cần order → LinkedHashSet
    cần sorted/range → TreeSet
  cần FIFO/LIFO hai đầu → ArrayDeque
  cần priority → PriorityQueue
  producer-consumer/backpressure → bounded BlockingQueue

7.1. Bổ sung · các nhánh trong sơ đồ không loại trừ nhau

Cách dùng: sơ đồ nguồn là điểm bắt đầu, không phải chuỗi if/else ưu tiên điều kiện đầu tiên. Hãy xét đồng thời identity, order, access pattern, ownership, mutability và boundedness rồi mới cân complexity.

Những nhu cầu kết hợp phải xét riêng
Nhu cầuỨng viênBằng chứng trước khi chốt
Shared + sorted/rangeConcurrentSkipListMap/Set [R23] [R24]Kiểm tra range semantics, concurrent updates và snapshot requirement.
Domain là enumEnumSet / EnumMap [R14] [R19]Domain cố định; null policy, thứ tự khai báo đáp ứng yêu cầu.
Read-mostly listener listCopyOnWriteArrayList [R09]Đo write cost, số listener, thời gian giữ iterator và allocation.
Priority + concurrent + giới hạn memoryPriorityBlockingQueue kèm admission policy riêng [R32]Không nhầm constructor initial capacity thành hard bound; test overload và starvation.
Shared + insertion/access orderThiết kế khóa/ownership bao quanh ordered map [R17] [R36]Lock phải bao phủ compound action; access-order get có thể sửa order.
Trả dữ liệu ra API không cho caller sửa containerUnmodifiable shallow copy phù hợp [R38]Quy định riêng mutable elements, copy timing và ownership.

7.2. Bổ sung · phiếu quyết định cho một workload

Bài tự kiểm tra đề xuất: chọn một luồng đang dùng collection và ghi một đoạn quyết định gồm: identity/duplicates; encounter order hay sorted/range; read/write ratio; thread ownership; null policy; capacity; iterator/copy semantics. Nêu một ứng viên bị loại và lý do. Không xem đây là lab nguồn hay tăng số lab của Part 01.

Evidence nên có: test identity/comparator, test aliasing view/copy, test update concurrent, test queue đầy, và measurement allocation/latency trên dữ liệu đại diện. Thông số máy, JDK, workload và cách đo phải đi kèm kết quả; trang này không đưa benchmark cố định.

8. Checklist phỏng vấn

8.1. Bổ sung · biến checklist thành bằng chứng

Đối chiếu từng mục trong 8 checklist nguồn
Mục nguồnĐạt khi giải thích / chứng minh được
1 · HierarchyVẽ riêng nhánh Map; giải thích collection views và superinterfaces sequenced của Java 21; chạy SequencedCheck.
2 · Semantics trước complexityViết được phiếu quyết định từ yêu cầu cụ thể, không chọn chỉ vì “O(1)”.
3 · Internals cơ bảnGiải thích backing array/resize, hash buckets/equality, red-black ordering, heap head; chuyển sang Internals cho call flow và 4 failure/performance labs mà nguồn dẫn tới.
4 · Các loại orderPhân biệt sequence/index, insertion order, access order và comparator order; không dựa PriorityQueue iterator để lấy sorted output.
5 · View/copy/immutableChạy ViewCopyCheck; chỉ ra được thay đổi nào truyền qua alias, thay đổi nào không.
6 · Ba loại iteratorChạy IteratorModesCheck; nói rõ vì sao không assert một snapshot toàn map hoặc một CME bắt buộc.
7 · Bounded queueChạy QueuePolicyCheck; giải thích timeout/reject policy và vì sao enqueue chưa chứng minh xử lý nghiệp vụ.
8 · LinkedList không mặc định nhanh hơnTách traversal cost khỏi insert/remove tại vị trí đã có; đưa cách đo cùng workload thay cho kết luận tuyệt đối.

8.2. Bổ sung · bốn câu hỏi đào sâu

Trả về unmodifiable view có ngăn dữ liệu đổi sau khi return không?

Không ngăn backing list bị sửa qua reference khác. Chọn shallow copy khi cần ổn định membership/order, và xử lý riêng mutable element graph. [R36] [R38]

ConcurrentHashMap có đủ cho “giảm A, tăng B” như một transaction không?

Không. Atomic operation theo một key không bao phủ hai key và cũng không bảo vệ database side effect. Cần boundary đồng bộ/transaction riêng và test invariant ở cấp nghiệp vụ. [R22]

Tại sao Set “làm mất” một trong hai BigDecimal cùng giá trị?

Kiểm tra implementation và định nghĩa equality. TreeSet dùng comparator equivalence; 1.01.00 compareTo bằng 0 nhưng equals khác nhau. Phải chọn identity phù hợp domain. [R13] [R15]

Tại sao dùng BlockingQueue vẫn có thể tăng bộ nhớ không giới hạn?

Chặn khi lấy từ queue rỗng không đồng nghĩa giới hạn chiều dài. PriorityBlockingQueue là logically unbounded; LinkedBlockingQueue không chỉ định capacity có bound cực lớn. Cần admission limit và overload policy. [R32] [R31]

Điểm dừng của chương: chọn collection có lập luận về contract, nhận diện failure mode và đưa evidence. Sáu chương trình là ví dụ kiểm chứng bổ sung trong trang; không thay thế bốn failure/performance labs ở chương Internals.