Part 01 · Java Core · 1.1

Lý thuyết Java Core

Bản diễn giải tiếng Việt dựa trên Java SE 21, JLS 21 và các JEP đã phát hành. Thuật ngữ tiếng Anh được giữ lại ở nơi cần tra cứu hoặc phỏng vấn.

Baseline: Java SE 2115 nhóm nền tảng12 khối code nguồnKhông dùng preview
Cách đọc: học các quy tắc nền trước, sau đó dùng phần Bổ sung để phân biệt contract của ngôn ngữ/API, hành vi implementation và trade-off triển khai. Các điểm tự kiểm yêu cầu output hoặc bằng chứng; chúng không phải bộ câu hỏi/lab chính thức của Part 01.
Phạm vi: đây là giáo trình hệ thống hóa, không phải bản sao nguyên văn đặc tả. Khi một quy tắc cần độ chính xác ngôn ngữ, liên kết JLS tương ứng được đặt ngay tại phần đó.
Trong bài này
  1. Mô hình thực thi
  2. Kiểu và biến
  3. Biểu thức và control flow
  4. Class và OOP
  5. Design Patterns
  6. Java hiện đại
  7. Equality và String
  8. Exception và resource
  9. Generics
  10. Collections
  11. Collections Internals
  12. Sorting algorithms
  13. Lambda và Stream
  14. Concurrency và JMM
  15. Async và virtual thread
  16. I/O, time và reflection
  17. JVM, GC và diagnostic
  18. Bản đồ chuyên đề mở rộng

1. Mã nguồn, bytecode, JDK và JVM

Java source được compiler kiểm tra cú pháp và kiểu rồi biên dịch thành bytecode trong file .class. JVM nạp class, kiểm tra bytecode, liên kết symbolic reference và thực thi bằng interpreter hoặc JIT compiler. Tính “write once, run anywhere” đến từ bytecode chuẩn cùng JVM tương ứng trên từng nền tảng; nó không có nghĩa mọi hành vi phụ thuộc hệ điều hành đều giống nhau.

JVM, JRE và JDK: phân biệt vai trò
Thành phầnVai trò
JVMMáy ảo thực thi bytecode, quản lý memory, thread, GC và runtime services.
JREKhái niệm môi trường chạy gồm JVM và thư viện runtime. Từ JDK 11, Oracle không còn cung cấp download JRE và Server JRE riêng; JDK 9–10 vẫn có các download này.
JDKJVM, thư viện và công cụ phát triển như javac, java, javadoc, jcmd.

Ví dụ nhập môn theo luồng hướng dẫn chính thức: chương trình đầu tiên

Ví dụ nguồn 01 · Lưu thành HelloJava.java
public class HelloJava {
    public static void main(String[] args) {
        System.out.println("Xin chào Java 21!");
    }
}
Ví dụ nguồn 02 · Chạy tại thư mục chứa HelloJava.java
javac HelloJava.java
java HelloJava

Tên file phải trùng public top-level class. main là entry point truyền thống; String[] args chứa argument từ command line.

Bổ sung · Phiên bản và ranh giới bảo đảm

Đừng trộn compiler, class loader và JIT

Contract: loading tìm biểu diễn class; linking gồm verification, preparation và resolution. Resolution có thể diễn ra sớm hoặc khi symbolic reference được dùng, không nhất thiết resolve toàn bộ chương trình trước main. Initialization mới thực thi static initializer theo quy tắc ngôn ngữ. Interpreting/JIT là cách triển khai runtime, không phải cam kết mọi method đều được JIT hay đều nhanh hơn sau một số lần gọi cố định. [R02 · JLS 12: Execution] · [R03 · JVMS 2: Runtime areas]

Hiệu chỉnh mốc JRE: JDK 9–10 vẫn có download JDK và JRE riêng của Oracle; mốc Oracle dừng cung cấp download JRE/Server JRE là JDK 11. “JRE” vẫn hữu ích như khái niệm môi trường chạy; cách đóng gói runtime phụ thuộc nhà cung cấp và deployment. [R01 · Oracle JDK 11 Migration Guide]

Thực hành build: lưu chương trình đầu tiên thành HelloJava.java, chạy lệnh ở thư mục chứa file. Kết quả là Xin chào Java 21!. Lệnh dưới ghi rõ encoding nguồn và release đích; --release không tự chứng minh dependency bên thứ ba tương thích runtime đích. [javac, Java 21]

Bổ sung · Biên dịch cùng baseline Java 21
javac -encoding UTF-8 --release 21 HelloJava.java
java HelloJava

Encoding đầu ra: nếu terminal hiển thị sai dấu tiếng Việt, kiểm riêng encoding/font của terminal và encoding source; biên dịch UTF-8 không tự cấu hình mọi console.

Tự kiểm bổ sung · bằng chứng cần có. Ghi lại java -version, javac -version, lệnh build và output. Giải thích được vì sao source hợp lệ nhưng vẫn có thể gặp lỗi liên kết hoặc lỗi môi trường khi chạy.

2. Hệ thống kiểu, biến và chuyển đổi

Java là ngôn ngữ statically typed: mỗi expression có kiểu được xác định lúc compile. Có tám primitive type: boolean, byte, short, int, long, char, float, double. Reference type gồm class, interface, array và type variable. Biến reference giữ một reference hoặc null, không chứa trực tiếp object.

Field được gán default value; local variable phải được compiler chứng minh đã khởi tạo trước khi đọc. Numeric promotion và conversion tuân theo quy tắc cụ thể; narrowing conversion có thể mất dữ liệu nên thường cần cast.

Ví dụ nguồn 03 · Biến local: đặt trong main hoặc JShell
int exact = 100;
long widened = exact;        // widening: an toàn về miền giá trị int
int narrowed = (int) 3_000_000_000L; // overflow: kết quả không còn 3 tỷ

Java luôn truyền tham số theo giá trị

Method nhận một bản sao của giá trị argument. Với primitive, đó là bản sao số/boolean/char. Với object, đó là bản sao của reference. Hai biến có thể cùng trỏ đến một object nên mutation của object được quan sát từ cả hai nơi, nhưng gán lại parameter không đổi reference của caller.

Ví dụ nguồn 04 · Method trong class; Person có field name và constructor nhận String
static void rename(Person person) { person.name = "An"; }
static void replace(Person person) { person = new Person("Bình"); }
Bổ sung · Giá trị, reference và precision

Widening không đồng nghĩa giữ mọi chữ số

Contract: int → long giữ nguyên giá trị, nhưng một số widening sang floating-point có thể mất precision. Cast 3_000_000_000L sang int trong ví dụ nguồn cho -1294967296, không ném lỗi chỉ vì vượt miền int. Unboxing một wrapper null gây NullPointerException. Primitive không phải object; wrapper như Integer mới là reference type. [JLS 5]

Bổ sung · Chạy trong JShell hoặc bên trong main
int n = 16_777_217;
float f = n;
System.out.println((int) f); // 16777216: mat precision
Integer missing = null;
// int value = missing;     // Neu bo comment: NullPointerException

Đọc ví dụ truyền tham số: giả sử Person có field String name và constructor Person(String name). Với p = new Person("Lan"), rename(p) sửa object mà p đang trỏ tới thành tên “An”; replace(p) không thay object mà biến p của caller đang trỏ tới. final trên reference ngăn gán lại biến, không làm object được tham chiếu thành immutable.

Tự kiểm bổ sung · bằng chứng cần có. Dự đoán rồi chạy kết quả cast, unboxing và hai method rename/replace. Vẽ hai biến reference cùng trỏ một object, thay vì mô tả Java là pass-by-reference.

3. Expression, statement và control flow

Expression tạo ra một value hoặc side effect; statement tạo nên luồng thực thi. Cần nắm precedence nhưng nên dùng ngoặc khi biểu thức khó đọc. &&|| short-circuit; toán hạng phải không còn cần thiết thì phần sau không chạy. switch hiện có thể là expression và yêu cầu các nhánh phủ đủ miền giá trị.

Ví dụ nguồn 05 · Trong method; Status là enum NEW, RUNNING, DONE
String label = switch (status) {
    case NEW -> "Mới";
    case RUNNING -> "Đang chạy";
    case DONE -> "Hoàn tất";
};

Array có độ dài cố định, là object và kiểm tra bounds tại runtime. Enhanced for phù hợp khi không cần index hoặc sửa cấu trúc collection.

Bổ sung · Exhaustiveness và null

“Phủ đủ nhánh” có phạm vi cụ thể

Ví dụ nguồn giả định enum Status { NEW, RUNNING, DONE } và một biến Status status khác null. Mỗi nhánh trả một nhãn; block nhiều statement trong switch expression dùng yield để trả giá trị. Không nhầm với switch kiểu case ...: có thể fall-through. [JLS 14] · [JLS 15]

Contract: exhaustive khi compile không đồng nghĩa null-safe. Với Java 21, selector reference bằng null cần nhánh case null nếu muốn xử lý nó trong switch; chỉ có default không bắt null. Khi enum/sealed hierarchy được biên dịch lại riêng và thêm biến thể, binary cũ cũng không được bảo đảm xử lý mọi giá trị mới. [JEP 441]

Quy tắc đọc code: trong x != null && x.isReady(), điều kiện đầu bảo vệ lời gọi sau. Enhanced for không cấp quyền sửa cấu trúc collection tùy ý: chọn API sửa phù hợp với implementation, thay vì trông chờ vòng lặp tự xử lý mutation.

Tự kiểm bổ sung · bằng chứng cần có. Chạy cả ba giá trị enum và trường hợp null. Ghi rõ nhánh nào trả value, nhánh nào ném lỗi; kiểm tra một biểu thức short-circuit bằng bộ đếm side effect.

4. Class, interface và thiết kế hướng đối tượng

Class mô tả state và behavior; object là instance. Encapsulation bảo vệ invariant bằng cách giới hạn quyền sửa state. Inheritance biểu diễn quan hệ “is-a” và cho phép subtype polymorphism, nhưng tạo coupling mạnh. Composition biểu diễn “has-a”, thường linh hoạt và dễ test hơn.

Học sâu: xem chuyên đề OOP: 4 trụ cột, polymorphism, quan hệ object và SOLID với ví dụ Java và các cặp khái niệm thường bị nhầm.

Initialization

Static field/block chạy khi class được initialization. Instance field initializer và instance initializer chạy trước constructor body, sau phần constructor của superclass. Tránh gọi overridable method từ constructor vì subclass có thể chưa được khởi tạo đầy đủ.

Interface hay abstract class?

Lựa chọn interface hay abstract class
InterfaceAbstract class
Định nghĩa capability/contract; một class có thể implement nhiều interface.Chia sẻ state, protected implementation và constructor cho một hierarchy có quan hệ chặt.
Có abstract, default, static và private method theo phiên bản Java hiện đại.Có instance field và đầy đủ mức truy cập.

Nested class gồm static nested class và inner class. Inner instance giữ liên kết ngầm đến outer instance; điều này ảnh hưởng lifetime và memory, nên không dùng vô thức.

Bổ sung · Thiết kế theo invariant

Dispatch khác với lựa chọn overload

Contract: overload được chọn lúc compile theo kiểu khai báo của biểu thức và các quy tắc áp dụng method. Với instance method có thể override, runtime dispatch chọn implementation theo object thực tế. Static method được hide chứ không override như instance method; field access cũng không có dynamic dispatch theo cách đó. [JLS 8] · [JLS 15]

Phạm vi Java 21: “đã load class” chưa có nghĩa static block đã chạy. Trong quá trình tạo instance, superclass được khởi tạo trước phần instance initializer và constructor body tương ứng của subclass. Gọi method overridable từ constructor có thể đi vào subclass khi state subclass vẫn chưa hoàn chỉnh. [R02 · JLS 12: Execution]

Trade-off thiết kế: chọn inheritance khi subtype thực sự giữ contract và invariant của base type, không chỉ để dùng lại vài dòng code. Chọn composition khi cần thay dependency, test độc lập hoặc tránh ràng buộc lifecycle. Interface không phải “class không có code”: default/static/private method có vai trò riêng; abstract class phù hợp khi hierarchy cần state và construction chung.

Tự kiểm bổ sung · bằng chứng cần có. Viết trace thứ tự static initializer, instance initializer và constructor trên một hierarchy nhỏ; chỉ ra invariant bị phá nếu constructor gọi method override. Kèm một ví dụ tách overload khỏi override.

5. Record, sealed class và pattern matching

Record là dạng class dành cho aggregate dữ liệu trong suốt. Compiler tạo private final component fields, accessor, canonical constructor, equals, hashCodetoString. Record shallowly immutable: component reference không đổi, nhưng object được tham chiếu vẫn có thể mutable.

Ví dụ nguồn 06 · Khai báo record Money
record Money(long amount, String currency) {
    Money {
        if (amount < 0) throw new IllegalArgumentException("amount");
    }
}

Sealed class/interface kiểm soát trực tiếp các subtype được phép bằng permits. Nó hữu ích khi domain có tập biến thể hữu hạn. Pattern matching cho instanceofswitch kết hợp type test với binding variable, giúp code xử lý hierarchy rõ và exhaustiveness được compiler hỗ trợ.

Ví dụ nguồn 07 · Khai báo type ở cấp class/file; switch đặt trong method có PaymentResult result
sealed interface PaymentResult permits Paid, Declined {}
record Paid(String transactionId) implements PaymentResult {}
record Declined(String reason) implements PaymentResult {}

String message = switch (result) {
    case Paid(var id) -> "Thành công: " + id;
    case Declined(var reason) -> "Từ chối: " + reason;
};
Bổ sung · Mốc ổn định và data contract

Những tính năng ở đây không cần preview trên Java 21

Tính năngỔn định từPhạm vi cần nhớ
RecordJava 16 [JEP 395]Data carrier; không tự deep-copy component.
Pattern matching cho instanceofJava 16 [R04 · JEP 394]Binding variable có flow scope.
Sealed class/interfaceJava 17 [JEP 409]Kiểm soát direct subtype; subtype tiếp tục sealed, final hoặc non-sealed theo quy tắc.
Record patternsJava 21 [R05 · JEP 440]Paid(var id) là record pattern, không chỉ type pattern.
Pattern matching cho switchJava 21 [JEP 441]Exhaustiveness, dominance và null handling là các vấn đề riêng.

Để chạy đoạn PaymentResult, đặt các khai báo sealed/record trong cùng file bài tập, khai báo PaymentResult result = new Paid("tx-01"); trước switch trong method. Kết quả nhánh đó là Thành công: tx-01. Ví dụ Money chỉ minh họa compact constructor kiểm amount; chưa kiểm currency, đơn vị tiền hoặc toàn bộ invariant nghiệp vụ.

Bổ sung · Record sao chép cấu trúc list ở boundary
record Team(java.util.List<String> members) {
    Team {
        members = java.util.List.copyOf(members);
    }
}

Scope guarantee: List.copyOf ở đây tạo list không sửa được và tách khỏi thay đổi cấu trúc của list đầu vào; không deep-copy phần tử. Các phần tử đang là String immutable. Nếu thay bằng object mutable, vẫn phải định nghĩa ownership hoặc copy phần tử. [R06 · List API]

Tự kiểm bổ sung · bằng chứng cần có. Biên dịch bằng --release 21 không bật preview. Kiểm amount âm, nhánh Paid/Declined, null selector và mutation list đầu vào; giải thích record giữ được invariant nào, không giữ được invariant nào.

6. Equality, hash code, immutability và String

== so sánh primitive value hoặc identity của reference. equals biểu diễn logical equality do class định nghĩa. Contract yêu cầu reflexive, symmetric, transitive, consistent và trả false với null. Nếu hai object equal thì chúng bắt buộc có cùng hash code; chiều ngược lại không bắt buộc.

Key của hash-based collection nên giữ ổn định các field tham gia equals/hashCode. Thay đổi chúng sau khi insert có thể khiến entry “mất” trong bucket cũ.

String immutable, hỗ trợ sharing và string pool. Concatenation lặp trong loop nên dùng StringBuilder; StringBuffer synchronized và hiếm khi cần trong code hiện đại.

Bổ sung · Identity không phải nội dung

String pool không biến primitive thành object

Bổ sung · Chạy trong JShell hoặc main
String a = "abc";
String b = "ab" + "c";
String c = new String("abc");
System.out.println(a == b);         // true
System.out.println(a == c);         // false
System.out.println(a.equals(c));    // true
System.out.println(a == c.intern()); // true

Contract: literal và string constant expression có interning theo quy tắc ngôn ngữ; new String("abc") tạo object khác. intern() trả reference canonical; nó không đổi identity của biến c. String literal vẫn là object String. Metadata constant pool của class file và tập các object string được intern không phải cùng một khái niệm. [String API] · [JLS 15] · [R03 · JVMS 2: Runtime areas]

Trade-off: pooling không phải lý do để dùng == so nội dung String đến từ input. Không dựa vào integer wrapper cache để so logical value. Dùng key có equality ổn định; việc map đã giữ reference không “đóng băng” state của key. StringBuilder phù hợp tích lũy trong loop; một biểu thức nối chuỗi đơn giản không mặc nhiên cần viết lại thủ công.

Tự kiểm bổ sung · bằng chứng cần có. Ghi output bốn phép so sánh; kiểm các thuộc tính equals/hashCode và thử sửa field của key sau khi đưa vào map. Phân biệt một entry vẫn tồn tại với việc lookup bằng key còn thành công hay không.

7. Exception và quản lý tài nguyên

Checked exception phải được catch hoặc declare; unchecked bao gồm Error cùng các subclass, và RuntimeException cùng các subclass. Chọn exception theo khả năng caller xử lý có ý nghĩa, không theo thói quen. Không bắt rộng rồi bỏ qua; phải preserve cause khi translate exception.

Try-with-resources đóng resource theo thứ tự ngược lúc khai báo. Nếu body và close() cùng ném lỗi, lỗi từ body là primary, lỗi close nằm trong suppressed exceptions.

Ví dụ nguồn 08 · Method trả List<String>, nhận Path path; import java.io và java.nio.file
try (var reader = Files.newBufferedReader(path)) {
    return reader.lines().toList();
} catch (IOException cause) {
    throw new UncheckedIOException("Không đọc được " + path, cause);
}
Bổ sung · Lỗi chính, lỗi cleanup và khả năng phục hồi

Unchecked còn bao gồm Error

Contract đã hiệu chỉnh: nhóm unchecked gồm RuntimeException, Error và các subclass của chúng. catch (Exception e) không bắt Error. Không biến điều này thành quy tắc bắt Throwable rồi tiếp tục; nhiều Error phản ánh tình trạng ứng dụng không có khả năng phục hồi thông thường. [JLS 11]

Bổ sung · Lưu ResourceFailureDemo.java; output: body rồi close
import java.io.IOException;

public class ResourceFailureDemo {
    static final class Resource implements AutoCloseable {
        @Override
        public void close() throws IOException {
            throw new IOException("close");
        }
    }

    public static void main(String[] args) {
        try (var resource = new Resource()) {
            throw new IOException("body");
        } catch (IOException failure) {
            System.out.println(failure.getMessage());
            System.out.println(failure.getSuppressed()[0].getMessage());
        }
    }
}

Ví dụ đọc file nguồn là phần thân một method trả List<String> và nhận Path path, với import từ java.io, java.nio.file. Lỗi phát sinh trong traversal của reader.lines() có thể được bọc thành UncheckedIOException, không đi vào riêng nhánh catch (IOException) trong ví dụ. toList() giữ kết quả trong memory; file lớn cần chiến lược xử lý streaming/bounded thay vì mặc định tải toàn bộ. [R16 · BufferedReader API]

Review rule: log nguyên cause và suppressed exceptions ở boundary chịu trách nhiệm; không return/throw từ finally làm che mất lỗi gốc. Khi bắt InterruptedException, truyền lên hoặc khôi phục interrupt flag theo contract của layer, không nuốt tín hiệu dừng. [JLS 14] · [R09 · ExecutorService API]

Tự kiểm bổ sung · bằng chứng cần có. Chạy cả body-failure và close-failure; lưu primary exception, cause, suppressed list. Với I/O, kiểm file không tồn tại, lỗi lúc đọc và giới hạn kích thước đầu vào.

8. Generics và type erasure

Generics đưa type check về compile time. Java generics invariant: List<Integer> không phải subtype của List<Number>. Wildcard mô tả họ kiểu: ? extends T phù hợp nguồn đọc T, ? super T phù hợp đích nhận T — quy tắc PECS: Producer Extends, Consumer Super.

Type parameter phần lớn bị erase khi compile để tương thích binary. Vì vậy không thể new T(), không thể tạo array generic trực tiếp và không thể dùng một giá trị kiểu Object để kiểm tra nội dung generic bằng instanceof List<String>. Compiler có thể sinh bridge method để giữ polymorphism sau erasure.

Bổ sung · PECS và ranh giới runtime

Wildcard là giới hạn phép toán, không phải phép đổi kiểu miễn phí

Bổ sung · Method đặt trong class bài tập
static <T> void copyAll(
        java.util.List<? extends T> source,
        java.util.List<? super T> target) {
    for (T value : source) {
        target.add(value);
    }
}

Với ? extends T, đọc phần tử như T nhưng không thêm tùy ý một T khác null vì kiểu cụ thể có thể hẹp hơn. Với ? super T, thêm T được nhưng đọc chỉ chắc chắn là Object. Đây là quy tắc type safety, không đảm bảo collection hỗ trợ mutation tại runtime; target không sửa được vẫn có thể ném UnsupportedOperationException.

Hiệu chỉnh phát biểu instanceof: không thể từ một giá trị có kiểu Object dùng instanceof List<String> để xác nhận kiểu của mọi phần tử. Dùng List<?> để kiểm “là list”, rồi validate nội dung ở boundary. Tuy nhiên, Java 16+ cho phép một số phép kiểm parameterized type đã an toàn từ static type, ví dụ List<String> x với x instanceof ArrayList<String>. Vì vậy không được diễn giải erasure thành “mọi instanceof có generic đều bị cấm”. [JLS 15]

Trade-off: tránh raw type và unchecked cast khi có thể diễn đạt bằng generic signature. Compile với cảnh báo và kiểm boundary deserialize/input. Erasure không xóa toàn bộ metadata generic khỏi class file; vấn đề là runtime không kiểm từng type argument như một reified container contract.

Tự kiểm bổ sung · bằng chứng cần có. Gọi copyAll từ List<Integer> sang List<Number>; thử đảo chiều để thấy compiler từ chối. Kiểm target không sửa được và tách compile-time type safety khỏi khả năng mutation runtime.

9. Collections Framework

Collections: đặc tính và trường hợp sử dụng
Cấu trúcĐặc tính chínhDùng khi
ArrayListRandom access O(1), append amortized O(1), chèn/xóa giữa O(n).Danh sách tổng quát, đọc/duyệt nhiều.
HashMap/HashSetTra cứu trung bình O(1), không đảm bảo thứ tự.Lookup theo key hoặc uniqueness.
LinkedHashMapGiữ insertion/access order.Ordered map hoặc nền cho LRU.
TreeMap/TreeSetSorted, thao tác O(log n).Cần range query hoặc thứ tự.
ArrayDequeThêm/xóa hai đầu hiệu quả.Stack hoặc queue trong single-thread.
ConcurrentHashMapConcurrent access, compound operation phải dùng API atomic.Shared map nhiều thread.

HashMap dùng hash để chọn bucket rồi dùng equals phân biệt key. Collision là bình thường. Capacity và load factor quyết định lúc resize. Từ Java 8, bucket quá đông có thể chuyển sang tree khi thỏa điều kiện implementation; không nên phụ thuộc các ngưỡng nội bộ như contract API.

Bổ sung · Ownership, view và snapshot

Không sửa được chưa chắc là snapshot

Bổ sung · Chạy trong JShell hoặc main
var original = new java.util.ArrayList<>(java.util.List.of("A"));
var view = java.util.Collections.unmodifiableList(original);
var snapshot = java.util.List.copyOf(original);
original.add("B");
System.out.println(view);     // [A, B]
System.out.println(snapshot); // [A]

Contract: unmodifiable view vẫn phản ánh backing collection; List.copyOf không phản ánh những thay đổi cấu trúc sau đó của nguồn. Cả hai không tự deep-copy phần tử; các factory List.of/copyOf không nhận phần tử null. [R06 · List API] · [R07 · Collections API]

Scope của bảng chọn cấu trúc: O(1) của hash lookup là điều kiện trung bình với phân bố hash phù hợp, không phải latency SLO. Access order của LinkedHashMap cần chế độ cấu hình tương ứng; map có order không tự trở thành cache có giới hạn hoặc thread-safe. Tree ordering phải được chọn nhất quán với logical equality mong muốn.

Vận hành: fail-fast iterator của HashMap chỉ cố gắng phát hiện structural modification, không phải cơ chế đồng bộ và không đảm bảo luôn ném ConcurrentModificationException. Với shared state, chọn đúng collection và atomic operation; không ghép containsKey rồi put và mặc nhiên cho là atomic. [HashMap API]

Tự kiểm bổ sung · bằng chứng cần có. So sánh view với snapshot; kiểm duplicate key, collision, mutation key và thứ tự duyệt. Khi đo hiệu năng, ghi kích thước dữ liệu, read/write ratio và số thread thay vì chỉ nêu Big-O.
Học sâu: xem kiến trúc Java Collections để nắm hierarchy interface, implementations, views, ordering và cách chọn collection.

10. Lambda, Stream và Optional

Lambda là implementation của functional interface — interface có một abstract method theo contract. Biến local được capture phải final hoặc effectively final. Stream mô tả pipeline xử lý dữ liệu; intermediate operation thường lazy, terminal operation kích hoạt traversal. Stream không phải collection và thường chỉ dùng một lần.

Ví dụ nguồn 09 · Trong method có List<String> words; import util, function và stream
Map<String, Long> frequency = words.stream()
    .map(word -> word.toLowerCase(Locale.ROOT))
    .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

Parallel stream trong OpenJDK thường dùng common ForkJoinPool; đây không phải quyền chọn executor do Stream API bảo đảm, và parallel không mặc định nhanh hơn. Tránh với workload nhỏ, blocking I/O, shared mutable state hoặc môi trường cần kiểm soát executor. Optional thích hợp làm return type biểu diễn có/không; thường không nên dùng làm field entity, parameter phổ thông hoặc gọi get() thiếu kiểm tra.

Bổ sung · Pipeline không phải vòng lặp có side effect

Lazy execution không bảo đảm mọi lambda đều chạy

Ví dụ nguồn giả định List<String> words không chứa null và import Map, Locale, Function, Collectors. Với ["Java", "JAVA", "JVM"], kết quả đếm là java=2, jvm=1; không suy ra thứ tự in map. Locale.ROOT biểu đạt chủ ý normalize độc lập locale hiển thị, không phải giải pháp đầy đủ cho mọi quy tắc tìm kiếm ngôn ngữ.

Contract: behavioral parameter phải non-interfering và thường stateless; stream implementation có thể lược bỏ operation nếu không làm đổi kết quả. Đừng đặt business side effect bắt buộc trong peek/map rồi coi đó là guarantee. Một stream không được traversal lại sau terminal operation. Captured reference effectively-final vẫn có thể trỏ tới object mutable, do đó không tự giải quyết thread safety. [Stream API]

Scope implementation: common ForkJoinPool là hành vi thường gặp của parallel stream trong OpenJDK, không phải quyền chọn executor được Stream API bảo đảm. Với blocking I/O hoặc nhu cầu isolation, thiết kế executor riêng thay vì dựa vào mẹo phụ thuộc implementation. Đo sequential và parallel bằng cùng workload trước khi chọn.

Optional: ưu tiên biểu đạt absent/present ở return type. orElse nhận một giá trị đã được tính theo quy tắc gọi method; orElseGet gọi supplier khi cần giá trị thay thế. Vì vậy fallback tốn kém hoặc có side effect nên được cân nhắc riêng. [Optional API] · [JLS 15]

Tự kiểm bổ sung · bằng chứng cần có. Kiểm pipeline rỗng, dữ liệu lặp và input null theo contract đã chọn. Đếm số lần fallback được gọi; không dùng side effect của một intermediate operation làm bằng chứng business correctness.

11. Thread, synchronization và Java Memory Model

Race condition xuất hiện khi kết quả phụ thuộc interleaving không được kiểm soát. Ba khái niệm cần tách biệt: atomicity (thao tác không bị xen giữa), visibility (thread thấy write của thread khác), ordering (ràng buộc thứ tự quan sát).

synchronized cung cấp mutual exclusion và quan hệ happens-before khi unlock/lock cùng monitor. volatile cung cấp visibility và ordering cho read/write biến đó, nhưng compound action như count++ vẫn không atomic. Atomic classes cung cấp CAS-based atomic operations. Lock API phù hợp khi cần timed/interruptible acquisition, nhiều condition hoặc chiến lược lock nâng cao.

Ví dụ nguồn 10 · Trong method; import concurrent và concurrent.atomic
AtomicInteger count = new AtomicInteger();
count.incrementAndGet();

ConcurrentHashMap<String, LongAdder> counters = new ConcurrentHashMap<>();
counters.computeIfAbsent("ok", key -> new LongAdder()).increment();

Deadlock cần bốn điều kiện kinh điển; biện pháp thiết thực gồm global lock ordering, giảm phạm vi lock, tránh gọi external code khi giữ lock và dùng timeout khi phù hợp.

Bổ sung · Happens-before và invariant nhiều thao tác

Visibility không biến read–modify–write thành atomic

Contract: unlock monitor happens-before lock sau đó trên cùng monitor; write volatile happens-before các read sau đó của chính biến volatile ấy. Quan hệ này có thể publish cả những write trước đó, không chỉ giá trị flag. Nó không khóa một chuỗi thao tác read → tính → write như count++. Nhiều field cùng tạo một invariant cần protocol bảo vệ chung. [JLS 17]

Bổ sung · Nested class; một lần publish, không reset hay sửa payload sau đó
static final class Message {
    private int payload;
    private volatile boolean ready;

    void publish() {
        payload = 42;
        ready = true;
    }

    int readWhenReady() {
        return ready ? payload : -1;
    }
}

Trong protocol một-lần ở trên, đọc ready == true cho phép quan sát payload == 42; không được tổng quát hóa sang nhiều writer hoặc tái sử dụng instance không có protocol mới. LongAdder trong ví dụ nguồn phù hợp counter thống kê có contention: sum() không là atomic snapshot khi vẫn có update, nên không dùng nó làm phép check-and-reserve quota. [R08 · LongAdder API]

Deadlock: bốn điều kiện kinh điển là mutual exclusion, hold-and-wait, no preemption và circular wait. Khi review, vẽ graph thứ tự lấy lock và external call. Timeout giúp thoát chờ theo policy, không tự bảo đảm dữ liệu đang sửa đã rollback hay không còn deadlock ở nơi khác.

Tự kiểm bổ sung · bằng chứng cần có. So counter thường, volatile và AtomicInteger sau khi join toàn bộ worker. Không quan sát được race trong một lần chạy không phải chứng minh an toàn; cần chỉ rõ happens-before và invariant mà lock/CAS bảo vệ.

12. Executor, CompletableFuture và virtual threads

ExecutorService tách việc submit task khỏi cách thread được quản lý. Luôn xác định lifecycle và shutdown. CompletableFuture mô tả graph xử lý bất đồng bộ: thenApply biến đổi value, thenCompose nối operation trả future, allOf phối hợp nhiều nhánh; phải thiết kế timeout, cancellation và exception path.

Virtual thread là lightweight Thread do JVM schedule, phù hợp workload có rất nhiều task chủ yếu chờ I/O. Nó giúp giữ code thread-per-request dễ đọc; không làm CPU chạy nhanh hơn và không loại bỏ giới hạn database connection hoặc downstream service. Không pool virtual thread; tạo mỗi task một thread và giới hạn tài nguyên khan hiếm bằng semaphore/pool/bulkhead.

Ví dụ nguồn 11 · Trong method có callRemoteService; xử lý InterruptedException và ExecutionException
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    Future<String> result = executor.submit(() -> callRemoteService());
    System.out.println(result.get());
}
Bổ sung · Timeout, cancellation và tài nguyên hữu hạn

Future hoàn tất không có nghĩa công việc nền đã dừng

Contract: các stage async của CompletableFuture không truyền executor thường dùng common pool theo API; stage không có Async có thể chạy trên thread hoàn tất stage. allOf trả CompletableFuture<Void>, không tự trả list kết quả. orTimeout làm future hoàn tất lỗi khi quá hạn, không có contract tự dừng I/O phía dưới; cancel(true) trên CompletableFuture cũng không bảo đảm interrupt computation. Phải thiết kế deadline và cancellation ở client/task thực tế. [CompletableFuture API]

Lifecycle: ví dụ nguồn giả định method bao quanh xử lý InterruptedException, ExecutionException và có callRemoteService(). get() đang chờ không giới hạn; ExecutorService.close() chờ task hoàn tất. Vì vậy chỉ thêm timeout cho get chưa bảo đảm toàn bộ block try-with-resources kết thúc đúng deadline. [R09 · ExecutorService API]

Version caveat: ở JDK 21, virtual thread blocking trong synchronized có thể pin carrier. JEP 491 đã thay đổi cơ chế monitor ở JDK 24, nên không sao chép nguyên lời khuyên “đổi mọi synchronized sang ReentrantLock” cho mọi JDK. Một số đường native callback vẫn có thể pin; đo trên đúng JDK triển khai. [JEP 444] · [R11 · JEP 491, JDK 24]

Trade-off: virtual threads không làm CPU-bound task nhanh hơn. Tạo thread theo task nhưng vẫn giới hạn concurrency vào database/downstream. Với platform-thread executor, xác định pool size, queue bound và rejection policy; queue vô hạn có thể chuyển overload thành tăng memory và thời gian chờ. Cần đo in-flight task, queue wait, timeout, rejected task và downstream saturation, không chỉ thread count. [JEP 444] · [R10 · ThreadPoolExecutor API]

Tự kiểm bổ sung · bằng chứng cần có. Làm downstream chậm hoặc không phản hồi; chứng minh deadline, cancellation và shutdown ở từng layer. Ghi lại task còn chạy sau timeout, giới hạn in-flight và hành vi khi executor từ chối task.

13. I/O, NIO.2, Date/Time và reflection

Path biểu diễn đường dẫn; Files cung cấp thao tác file. Ưu tiên API NIO.2 và try-with-resources cho stream tài nguyên. Không giả định separator, encoding hoặc case sensitivity giống nhau trên mọi hệ điều hành.

java.time cung cấp immutable, thread-safe types: Instant cho timestamp UTC, LocalDate cho ngày không timezone, ZonedDateTime cho thời điểm theo zone rules, Duration/Period cho khoảng thời gian. Lưu timestamp thường dùng Instant; chuyển timezone ở boundary hiển thị.

Reflection đọc type/member lúc runtime và có thể phá encapsulation, tăng coupling, giảm khả năng refactor/optimize. Framework dùng reflection có chủ đích; business code nên ưu tiên interface và explicit mapping.

Bổ sung · Boundary môi trường và lịch

Encoding, resource và timezone phải có chủ ý

Contract: stream từ Files.lines cần được đóng; dùng try-with-resources, chọn charset theo hợp đồng dữ liệu và không mặc định đọc toàn bộ file lớn vào heap. Đường dẫn và quyền truy cập thuộc môi trường triển khai; một đường dẫn hợp lệ trên máy phát triển không phải guarantee trên máy đích. [R15 · Files API]

Bổ sung · Trong method nhận Path path và khai báo throws IOException
try (var lines = java.nio.file.Files.lines(
        path, java.nio.charset.StandardCharsets.UTF_8)) {
    long nonBlank = lines.filter(line -> !line.isBlank()).count();
    System.out.println(nonBlank);
}

Date/time: Duration là lượng thời gian theo seconds/nanos; Period là lượng theo ngày/tháng/năm trong lịch ISO. Cộng “một ngày lịch” vào ZonedDateTime có thể khác cộng 24 giờ ở nơi đổi daylight saving time. LocalDate không tự xác định một instant vì thiếu giờ và zone. [R12 · Period API]

Reflection có giới hạn: setAccessible(true) không phải cách vượt qua mọi module boundary. JPMS và việc package được opens cho caller ảnh hưởng deep reflection; trySetAccessible() có thể trả false. Nếu framework cần mở package, ghi lại phạm vi tối thiểu và kiểm bằng đúng chế độ deployment, không chỉ classpath trên IDE. [R13 · AccessibleObject API]

Tự kiểm bổ sung · bằng chứng cần có. Đọc file UTF-8 có tiếng Việt; kiểm đóng resource khi exception. So cộng Period với Duration qua một mốc đổi offset và kiểm reflection trên package không mở trong một named module.

14. Runtime memory, GC và chẩn đoán

Mỗi thread có stack chứa frame của method call; heap chứa object dùng chung. Trong HotSpot, Metaspace chứa class metadata trong native memory. JVM còn có code cache, thread stacks, direct buffers và native allocations, vì vậy process memory không chỉ là Java heap.

GC thu hồi object không còn reachable từ GC roots. “Java có GC” không đồng nghĩa không có memory leak: giữ reference ngoài ý muốn vẫn khiến object reachable. Các nguồn leak phổ biến gồm static collection, cache không giới hạn, listener không unregister, ThreadLocal không cleanup và queue tăng vô hạn.

Triệu chứng runtime và hướng điều tra ban đầu
Triệu chứngBước kiểm tra đầu tiên
CPU caoOS process/thread CPU, nhiều thread dump liên tiếp, JFR.
Heap tăngGC log, class histogram, heap dump và retained size.
Treo/latency caoThread dump, lock contention, pool saturation, downstream latency.
Process memory cao nhưng heap ổnNative Memory Tracking, direct buffer, thread count, metaspace.
Ví dụ nguồn 12 · Thay <pid> bằng PID thật của process được phép chẩn đoán
jcmd <pid> Thread.print
jcmd <pid> GC.class_histogram
jcmd <pid> JFR.start name=investigation duration=60s filename=app.jfr
Bổ sung · Logical model khác layout vật lý

Không suy ra process memory chỉ từ -Xmx

Scope: JVM specification mô tả runtime areas về mặt logic, không bắt mọi JVM dùng cùng layout. “Metaspace trong native memory” là mô hình HotSpot. Stack của virtual thread được triển khai bằng các stack chunk trong heap; không thể lấy số virtual thread nhân một native stack cố định để dự đoán memory. [R03 · JVMS 2: Runtime areas] · [JEP 444]

GC không phải lifecycle API: object không còn strong-reachable mới có cơ hội được thu hồi theo cơ chế GC/reference processing; không có lời hứa thu hồi ngay tại cuối scope. GC không thay close() cho file, connection hay resource bên ngoài. Khi điều tra leak, tìm đường giữ reference và retained size, không chỉ tổng allocation.

An toàn chẩn đoán: GC.class_histogram được tài liệu jcmd xếp impact cao tùy heap; heap dump cần capacity và kiểm soát truy cập vì có thể chứa dữ liệu nhạy cảm. Thu thập trên đúng process với quyền được phép; cân nhắc ảnh hưởng latency trước khi chạy trên production. [jcmd, Java 21]

NMT: cần bật bằng startup option, ví dụ -XX:NativeMemoryTracking=summary. Nó có overhead và chỉ theo dõi phạm vi memory của HotSpot, không phải mọi allocation native của thư viện bên ngoài. Chọn baseline khi hệ thống đã ở trạng thái đại diện rồi so diff; đối chiếu với OS memory thay vì coi NMT bằng RSS. [R14 · Native Memory Tracking]

Bổ sung · Thay <pid> bằng PID thật; hai lệnh NMT yêu cầu đã bật NMT từ startup
jcmd <pid> VM.native_memory baseline
jcmd <pid> VM.native_memory summary.diff
jcmd <pid> Thread.dump_to_file -format=json threads.json

Với virtual threads, bổ sung dump theo lệnh Thread.dump_to_file thay vì coi Thread.print truyền thống là toàn cảnh mọi virtual thread. Mỗi loại dump có mức thông tin khác nhau; kết hợp JFR, GC log và dấu thời gian request để phân biệt CPU, lock, queue và downstream. [JEP 444] · [jcmd, Java 21]

Tự kiểm bổ sung · bằng chứng cần có. Nộp một timeline gồm tải vào, latency, heap sau GC, thread/pool state và một giả thuyết có kiểm chứng. Không kết luận memory leak chỉ từ RSS cao hay khẳng định đã hết leak chỉ vì một lần GC làm heap giảm.

15. Chuyên đề mở rộng

Bốn chuyên đề dưới đây đào sâu những phần thường bị rút gọn trong tài liệu ôn phỏng vấn nhưng rất đáng học để hiểu Java như một platform hoàn chỉnh.

15 · Runtime

Class loading, bytecode & JPMS

Loading, linking, initialization, delegation, module graph, service loading và custom runtime image.

16 · Platform API

I/O, networking, reflection & metadata

NIO.2, charset, serialization, HTTP Client, annotation, reflection, MethodHandle và proxy.

17 · Đồng thời

Concurrency nâng cao

Executor tuning, synchronizer, concurrent collections, Fork/Join, Flow, VarHandle và cancellation.

18 · JVM

GC & production diagnostics

G1, ZGC, heap sizing, GC log, JFR, heap dump, native memory và quy trình điều tra.

Bổ sung · Chọn đường học theo vấn đề

Từ nền tảng sang chuyên đề, rồi quay lại thực hành

Bốn thẻ phía trên giữ nguyên phạm vi chuyên đề mở rộng của nguồn. Chọn Class loading khi cần giải thích lỗi liên kết hoặc module boundary; chọn Platform API khi vướng charset, resource, HTTP hay metadata; chọn Concurrency nâng cao khi cần executor/cancellation protocol; chọn GC & diagnostics khi phải đi từ triệu chứng vận hành tới bằng chứng runtime.

Những nhánh đã dẫn ở đầu bài và trong nội dung vẫn là phần của lộ trình: OOP design, String Pool, Collections architecture, Collections Internals, Sorting algorithmsDesign Patterns.

Trang này có 15 nhóm kiến thức; con số 15–18 trên bốn thẻ là nhãn chuyên đề trong nguồn, không phải bốn nhóm bổ sung vào tổng 15 của trang. Điểm tự kiểm trong bài là phần bổ sung cho việc đọc lý thuyết, không thay hoặc cộng vào bộ câu hỏi/lab chính thức của Part 01.

Tự kiểm bổ sung · bằng chứng cần có. Chọn một failure scenario, nối nó với ít nhất hai nhóm nền và một chuyên đề. Sau đó chuyển sang Câu hỏi, Thực hành và Tự đánh giá để kiểm mức hiểu thay vì chỉ đánh dấu đã đọc.