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ề Thread và Multithreading 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ó:
New: Thread được tạo nhưng chưa được khởi chạy (
start()
chưa được gọi).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.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).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ặcLockSupport.park()
).Timed Waiting: Thread chờ có thời hạn (ví dụ:
Thread.sleep()
,wait(timeout)
, hoặcjoin(timeout)
).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:
Kế thừa lớp
Thread
.Triển khai giao diện
Runnable
.
2.1. Kế thừa lớp Thread
Thread
Tạo một lớp con kế thừa
Thread
và ghi đè phương thứcrun()
.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
Runnable
Triển khai giao diện
Runnable
và định nghĩa logic trong phương thứcrun()
.Tạo một đối tượng
Thread
và truyền đối tượngRunnable
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
Đị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ọistart()
.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émIllegalThreadStateException
.
Kết luận
Hiểu rõ các khái niệm cơ bản về Thread và Multithreading 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ặcRunnable
, ưu tiênRunnable
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