1.3 · Tự viết code

Lab Java Core

Các lab custom dùng JDK 21 và không cần framework. Mỗi bài có yêu cầu, tiêu chí hoàn thành, gợi ý và lời giải tham khảo có thể mở sau.

Cách chạy chung: tạo file đúng tên public class, chạy javac TenFile.java, sau đó java TenClass. Hãy bật compiler warning bằng javac -Xlint:all TenFile.java.

Lab 00 · Hello Java và command line

Mục tiêu: hiểu compile/run model, argument và exit path.

  1. Viết chương trình nhận tên từ argument đầu tiên.
  2. Nếu thiếu tên, in hướng dẫn sử dụng và kết thúc.
  3. In phiên bản Java đang chạy.
Mở lời giải tham khảo
public class HelloJava {
    public static void main(String[] args) {
        if (args.length == 0) {
            System.out.println("Cách dùng: java HelloJava <tên>");
            return;
        }
        System.out.printf("Xin chào %s! Java %s%n",
                args[0], System.getProperty("java.version"));
    }
}
javac -Xlint:all HelloJava.java
java HelloJava Lan

Lab 01 · Value object bất biến

Bài toán: xây Money gồm amount theo đơn vị nhỏ nhất và currency. Không chấp nhận amount âm/currency rỗng; hỗ trợ cộng hai Money cùng currency; equality đúng để dùng làm Map key.

Tiêu chí: immutable, validation tại construction, overflow không bị bỏ qua, khác currency phải báo lỗi rõ.

Gợi ý

Dùng record, compact constructor, Math.addExact và normalize currency bằng Locale.ROOT.

Mở lời giải tham khảo
import java.util.Locale;
import java.util.Objects;

public record Money(long amount, String currency) {
    public Money {
        if (amount < 0) throw new IllegalArgumentException("amount phải >= 0");
        currency = Objects.requireNonNull(currency, "currency")
                .trim().toUpperCase(Locale.ROOT);
        if (currency.length() != 3) throw new IllegalArgumentException("currency phải có 3 ký tự");
    }

    public Money add(Money other) {
        Objects.requireNonNull(other, "other");
        if (!currency.equals(other.currency)) {
            throw new IllegalArgumentException("Không thể cộng khác currency");
        }
        return new Money(Math.addExact(amount, other.amount), currency);
    }

    public static void main(String[] args) {
        System.out.println(new Money(10_000, "vnd").add(new Money(5_000, "VND")));
    }
}

Lab 02 · Phân tích tần suất từ

Bài toán: nhận một đoạn văn, chuẩn hóa chữ thường, tách từ, đếm tần suất và in 10 từ phổ biến nhất theo frequency giảm dần rồi alphabet tăng dần.

Yêu cầu mở rộng: bỏ stop words; không phụ thuộc locale mặc định; so sánh imperative và Stream; giải thích complexity.

Mở lời giải tham khảo
import java.util.Arrays;
import java.util.Comparator;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

public class WordFrequency {
    public static void main(String[] args) {
        String text = String.join(" ", args);
        Map<String, Long> counts = Arrays.stream(text.toLowerCase(Locale.ROOT).split("[^\\p{L}\\p{N}]+"))
                .filter(word -> !word.isBlank())
                .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

        counts.entrySet().stream()
                .sorted(Map.Entry.<String, Long>comparingByValue(Comparator.reverseOrder())
                        .thenComparing(Map.Entry.comparingByKey()))
                .limit(10)
                .forEach(entry -> System.out.printf("%-20s %d%n", entry.getKey(), entry.getValue()));
    }
}

Lab 03 · Race condition và invariant

Bài toán: nhiều thread chuyển tiền qua lại giữa hai tài khoản. Tổng số dư toàn hệ thống phải không đổi và không tài khoản nào âm.

  1. Viết phiên bản cố ý lỗi để tái hiện race.
  2. Sửa bằng lock ordering ổn định, sau đó thử biến thể optimistic version/conditional update và một atomic database operation.
  3. Chạy ít nhất 100.000 giao dịch và kiểm tra các invariant: balance không âm, tổng tiền được bảo toàn, retry cùng request id không double debit.
  4. Giải thích vì sao chỉ đổi balance thành AtomicLong chưa đủ và ghi bằng chứng conflict/retry của từng biến thể.
Gợi ý thiết kế

Mỗi account có id, balance và ReentrantLock. Khi transfer, lock account có id nhỏ trước. Kiểm tra balance và cập nhật cả hai account trong cùng critical section; unlock theo thứ tự ngược trong finally.

Mở phần lõi lời giải
static boolean transfer(Account from, Account to, long amount) {
    Account first = from.id() < to.id() ? from : to;
    Account second = first == from ? to : from;
    first.lock().lock();
    second.lock().lock();
    try {
        if (from.balance() < amount) return false;
        from.withdraw(amount);
        to.deposit(amount);
        return true;
    } finally {
        second.lock().unlock();
        first.lock().unlock();
    }
}

Đây chỉ là phần critical section; bạn phải tự viết Account, workload, executor shutdown và assertion tổng tiền.

Lab 04 · CompletableFuture có timeout

Bài toán: gọi song song profile, orders và recommendations; profile là bắt buộc, hai nhánh còn lại có fallback; toàn request có deadline.

Khung code
try (var executor = Executors.newFixedThreadPool(8)) {
    var profile = CompletableFuture.supplyAsync(() -> loadProfile(userId), executor);
    var orders = CompletableFuture.supplyAsync(() -> loadOrders(userId), executor)
            .exceptionally(cause -> List.of());
    var recommendations = CompletableFuture
            .supplyAsync(() -> loadRecommendations(userId), executor)
            .completeOnTimeout(List.of(), 300, TimeUnit.MILLISECONDS);

    return profile.thenCombine(orders, UserView::withOrders)
            .thenCombine(recommendations, UserView::withRecommendations)
            .orTimeout(800, TimeUnit.MILLISECONDS)
            .join();
}

Hãy tự định nghĩa domain type và test nhánh timeout/failure. Trong ứng dụng thật, cân nhắc propagation cancellation đến I/O client.

Lab 05 · Virtual thread và bulkhead

Bài toán: chạy 10.000 tác vụ blocking mô phỏng remote call, nhưng downstream chỉ cho phép tối đa 50 request đồng thời.

  1. Tạo một virtual thread cho mỗi task.
  2. Dùng Semaphore làm bulkhead 50 permit.
  3. Đo throughput, p95/p99 latency, memory, connection-wait time, peak concurrency và số lỗi; không chỉ đếm threads.
  4. Thử bỏ semaphore và giải thích vì sao “chạy được nhiều thread” không đồng nghĩa downstream chịu được.
  5. Ghi JDK/library version và kiểm tra pinning do native/foreign call hoặc long-running synchronized; lưu ý hành vi monitor pinning của JDK 21 khác JDK 24 sau JEP 491.
Mở lời giải lõi
var bulkhead = new Semaphore(50);
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    var tasks = IntStream.range(0, 10_000)
            .mapToObj(index -> executor.submit(() -> {
                bulkhead.acquire();
                try {
                    Thread.sleep(Duration.ofMillis(100));
                    return index;
                } finally {
                    bulkhead.release();
                }
            })).toList();
    for (var task : tasks) task.get();
}

Production code phải xử lý interruption, deadline, observability và rejection/cancellation semantics rõ ràng.

Lab 06 · Plugin bằng ServiceLoader

Bài toán: tạo module ứng dụng khai báo uses một interface formatter và hai provider module khai báo provides ... with. Ứng dụng tìm provider mà không import implementation.

Gợi ý cấu trúc
formatter.api       exports api.Formatter
formatter.json      requires formatter.api; provides api.Formatter with json.JsonFormatter
formatter.app       requires formatter.api; uses api.Formatter

Lab 07 · File analyzer không rò resource

Bài toán: duyệt directory tree, chỉ đọc file UTF-8 nhỏ hơn giới hạn, đếm dòng/từ và xuất report. Phải xử lý symbolic link, file lỗi quyền và cancellation.

Lab 08 · Điều tra JVM

Bài toán: viết chương trình có ba chế độ: allocation pressure, lock contention và class-loader retention. Chạy từng chế độ rồi thu JFR, thread dump và class histogram.

java -Xms128m -Xmx128m -Xlog:gc* JvmIncident allocation
jcmd <pid> JFR.start settings=profile duration=60s filename=incident.jfr
jcmd <pid> Thread.print -l
jcmd <pid> GC.class_histogram

Báo cáo: triệu chứng, giả thuyết, bằng chứng, kết luận, fix và rủi ro khi thu heap dump ở production.

Rubric chấm lab

MứcTiêu chí
0 · Chưa đạtKhông chạy hoặc chỉ có happy path, không giải thích được lỗi.
1 · Đạt cơ bảnChạy đúng, có validation và xử lý lỗi tối thiểu.
2 · TốtCó test edge/concurrency, resource lifecycle và giải thích complexity.
3 · SeniorNêu invariant, failure mode, trade-off, observability và giới hạn production.