1. Thread vs MultiThread

Cơ bản về Thread và Multithreading trong Java

Phần này giới thiệu các khái niệm cơ bản về ThreadMultithreading trong Java, bao gồm định nghĩa, vòng đời, cách tạo thread, so sánh thread và process, ưu tiên thread, và thread nền (daemon threads). Nội dung được trình bày chi tiết, kèm ví dụ minh họa để dễ hiểu.


1. Khái niệm Thread

Thread là gì?

  • Thread (luồng) là đơn vị thực thi nhỏ nhất trong Java, đại diện cho một chuỗi lệnh được thực thi độc lập trong một chương trình.

  • Thread cho phép thực hiện nhiều tác vụ đồng thời (concurrency) trong một ứng dụng, tận dụng hiệu quả tài nguyên CPU, đặc biệt trên các hệ thống đa nhân.

  • Trong Java, mỗi thread được quản lý bởi JVM (Java Virtual Machine) và chạy trong một process duy nhất.

Vòng đời của Thread

Một thread trong Java trải qua các trạng thái sau trong vòng đời của nó:

  1. New: Thread được tạo nhưng chưa được khởi chạy (start() chưa được gọi).

  2. Runnable: Thread đã được khởi chạy (start() được gọi) và sẵn sàng để chạy. Thread có thể đang chạy hoặc chờ CPU cấp phát thời gian.

  3. Blocked: Thread bị khóa khi đang chờ một monitor lock (ví dụ: vào khối synchronized mà thread khác đang giữ khóa).

  4. Waiting: Thread tạm dừng vô thời hạn, chờ một sự kiện cụ thể (ví dụ: gọi wait(), join(), hoặc LockSupport.park()).

  5. Timed Waiting: Thread chờ có thời hạn (ví dụ: Thread.sleep(), wait(timeout), hoặc join(timeout)).

  6. Terminated: Thread hoàn thành công việc hoặc bị dừng (kết thúc phương thức run() hoặc ngoại lệ không được xử lý).

Hình minh họa vòng đời Thread:

New → Runnable ↔ Blocked/Waiting/Timed Waiting → Terminated

Ví dụ minh họa trạng thái Thread:

public class ThreadLifecycleExample {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                System.out.println("Thread state: " + Thread.currentThread().getState());
                Thread.sleep(1000); // Timed Waiting
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        System.out.println("Thread state (New): " + thread.getState()); // NEW
        thread.start();
        System.out.println("Thread state (Runnable): " + thread.getState()); // RUNNABLE
        Thread.sleep(100);
        System.out.println("Thread state (Timed Waiting): " + thread.getState()); // TIMED_WAITING
        thread.join();
        System.out.println("Thread state (Terminated): " + thread.getState()); // TERMINATED
    }
}

2. Tạo Thread

Java cung cấp hai cách chính để tạo thread:

  1. Kế thừa lớp Thread.

  2. Triển khai giao diện Runnable.

2.1. Kế thừa lớp Thread

  • Tạo một lớp con kế thừa Thread và ghi đè phương thức run().

  • Gọi start() để bắt đầu thực thi thread.

Ví dụ:

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread running: " + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start(); // Bắt đầu thread
    }
}

2.2. Triển khai giao diện Runnable

  • Triển khai giao diện Runnable và định nghĩa logic trong phương thức run().

  • Tạo một đối tượng Thread và truyền đối tượng Runnable vào constructor của nó.

Ví dụ:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Thread running: " + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        Runnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

Sự khác biệt và ưu nhược điểm

Tiêu chí

Kế thừa Thread

Triển khai Runnable

Cách triển khai

Kế thừa lớp Thread, ghi đè run().

Triển khai Runnable, truyền vào Thread.

Tính linh hoạt

Ít linh hoạt, không thể kế thừa lớp khác.

Linh hoạt hơn, có thể kế thừa lớp khác.

Tái sử dụng

Không tái sử dụng được logic run().

Logic run() có thể tái sử dụng.

Khuyến nghị

Dùng khi cần tùy chỉnh hành vi của Thread.

Thường được khuyến nghị cho các tác vụ đơn giản.

Lưu ý: Từ Java 8, có thể sử dụng lambda expression để tạo Runnable ngắn gọn:

Thread thread = new Thread(() -> System.out.println("Thread running"));
thread.start();

3. Thread vs Process

So sánh Thread và Process

Tiêu chí
Thread
Process

Định nghĩa

Đơn vị thực thi trong một process.

Chương trình đang chạy, có không gian riêng.

Bộ nhớ

Chia sẻ cùng không gian bộ nhớ của process.

Có không gian bộ nhớ riêng biệt.

Tài nguyên

Sử dụng chung tài nguyên (file, socket).

Tài nguyên độc lập, giao tiếp qua IPC.

Tốc độ tạo

Nhanh hơn, ít tốn tài nguyên.

Chậm hơn, tốn nhiều tài nguyên hơn.

Tính phụ thuộc

Phụ thuộc vào process, chết nếu process chết.

Độc lập, có thể chạy riêng biệt.

Ứng dụng

  • Thread: Dùng khi cần thực hiện nhiều tác vụ trong cùng một chương trình (ví dụ: xử lý giao diện người dùng và tải dữ liệu đồng thời).

  • Process: Dùng khi cần chạy các chương trình độc lập hoặc cần cách ly mạnh (ví dụ: chạy nhiều instance của một ứng dụng).

Ví dụ minh họa Thread trong Process:

  • Một ứng dụng Java (process) có thể có thread chính (main thread) và các thread phụ để xử lý tác vụ nền (background tasks).


4. Thread Priority

Thread Priority là gì?

  • Mỗi thread trong Java có một độ ưu tiên (priority), quyết định mức độ ưu tiên CPU cấp phát thời gian cho thread.

  • Độ ưu tiên được biểu diễn bằng số nguyên từ 1 đến 10:

    • Thread.MIN_PRIORITY (1): Ưu tiên thấp nhất.

    • Thread.NORM_PRIORITY (5): Ưu tiên mặc định.

    • Thread.MAX_PRIORITY (10): Ưu tiên cao nhất.

Cách thiết lập Priority

  • Sử dụng phương thức setPriority(int priority) để thay đổi độ ưu tiên.

  • Lấy độ ưu tiên hiện tại bằng getPriority().

Ví dụ:

public class ThreadPriorityExample {
    public static void main(String[] args) {
        Thread lowPriority = new Thread(() -> {
            System.out.println("Low priority thread: " + Thread.currentThread().getName());
        });
        Thread highPriority = new Thread(() -> {
            System.out.println("High priority thread: " + Thread.currentThread().getName());
        });

        lowPriority.setPriority(Thread.MIN_PRIORITY);
        highPriority.setPriority(Thread.MAX_PRIORITY);

        lowPriority.start();
        highPriority.start();
    }
}

Ảnh hưởng của Priority

  • Hiệu quả không đảm bảo: Độ ưu tiên không đảm bảo thread có ưu tiên cao sẽ luôn chạy trước, vì điều này phụ thuộc vào hệ điều hành và scheduler.

  • Khuyến nghị: Tránh phụ thuộc quá nhiều vào priority, chỉ sử dụng khi cần điều chỉnh hành vi rõ ràng (ví dụ: thread xử lý giao diện người dùng có ưu tiên cao hơn thread xử lý log).


5. Daemon Threads

Daemon Thread là gì?

  • Daemon Thread (thread nền) là thread chạy ở chế độ nền, phục vụ các tác vụ hỗ trợ (ví dụ: garbage collection, logging).

  • Daemon thread tự động bị hủy khi tất cả các non-daemon threads (user threads) trong chương trình kết thúc.

  • Mặc định, thread được tạo là user thread (non-daemon).

Cách tạo Daemon Thread

  • Sử dụng phương thức setDaemon(true) trước khi gọi start().

  • Kiểm tra trạng thái daemon bằng isDaemon().

Ví dụ:

public class DaemonThreadExample {
    public static void main(String[] args) {
        Thread daemonThread = new Thread(() -> {
            while (true) {
                System.out.println("Daemon thread running");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        daemonThread.setDaemon(true); // Đặt thread là daemon
        daemonThread.start();

        // Main thread kết thúc sau 3 giây
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Main thread terminated");
    }
}

Kết quả:

  • Daemon thread sẽ dừng khi main thread (non-daemon) kết thúc sau 3 giây, dù vòng lặp trong daemon thread là vô hạn.

Ứng dụng của Daemon Threads

  • Garbage Collection: Thread của JVM để dọn dẹp bộ nhớ.

  • Logging: Ghi log nền trong ứng dụng.

  • Monitoring: Theo dõi trạng thái hệ thống (ví dụ: thread kiểm tra tài nguyên CPU/memory).

Lưu ý

  • Không nên sử dụng daemon thread cho các tác vụ quan trọng (ví dụ: lưu dữ liệu vào database), vì chúng có thể bị hủy đột ngột.

  • Gọi setDaemon() sau khi thread đã khởi chạy sẽ ném IllegalThreadStateException.


Kết luận

Hiểu rõ các khái niệm cơ bản về ThreadMultithreading là nền tảng để làm việc với concurrency trong Java. Bạn cần nắm vững:

  • Vòng đời thread để quản lý trạng thái thread hiệu quả.

  • Cách tạo thread thông qua Thread hoặc Runnable, ưu tiên Runnable cho tính linh hoạt.

  • Thread vs Process để áp dụng đúng ngữ cảnh.

  • Thread Priority để điều chỉnh hành vi thread khi cần.

  • Daemon Threads để xử lý các tác vụ nền.

Last updated