Tạo và quản lý Thread
Sau bài này, bạn sẽ:
- Biết 3 cách tạo Thread trong Java (extends Thread, implements Runnable, Callable)
- Hiểu sự khác biệt giữa Thread và Runnable
- Sử dụng được các methods quản lý thread: start(), sleep(), join(), interrupt()
- Nắm được khái niệm daemon thread và thread priority
- Biết cách đặt tên threads để dễ debug
Bài trước: Giới thiệu Multithreading — Đã tìm hiểu về Process, Thread, Concurrency và Race Conditions. Bài này sẽ hướng dẫn chi tiết cách tạo và quản lý threads trong Java.
3 cách tạo Thread trong Java
Cách 1: Extends Thread class
public class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
// Code thực thi trong thread
for (int i = 1; i <= 5; i++) {
System.out.println(name + " - Count: " + i);
try {
Thread.sleep(1000); // Nghỉ 1 giây
} catch (InterruptedException e) {
System.out.println(name + " interrupted");
}
}
}
public static void main(String[] args) {
MyThread t1 = new MyThread("Thread-1");
MyThread t2 = new MyThread("Thread-2");
t1.start(); // Bắt đầu thread 1
t2.start(); // Bắt đầu thread 2
System.out.println("Main thread continues...");
}
}
Output (không theo thứ tự cố định):
Main thread continues...
Thread-1 - Count: 1
Thread-2 - Count: 1
Thread-1 - Count: 2
Thread-2 - Count: 2
...
Hạn chế: Java chỉ cho phép single inheritance. Nếu class đã extends class khác thì không thể extends Thread.
Cách 2: Implements Runnable interface (Recommended)
public class MyRunnable implements Runnable {
private String name;
public MyRunnable(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(name + " - Count: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(name + " interrupted");
}
}
}
public static void main(String[] args) {
MyRunnable r1 = new MyRunnable("Runnable-1");
MyRunnable r2 = new MyRunnable("Runnable-2");
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
Lambda expression (Java 8+):
public class RunnableLambdaDemo {
public static void main(String[] args) {
// Cách ngắn gọn với lambda
Thread t1 = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println("Lambda Thread - " + i);
try { Thread.sleep(1000); }
catch (InterruptedException e) { }
}
});
t1.start();
}
}
- Flexibility: Class có thể extends class khác và implements Runnable
- Separation of concerns: Logic (Runnable) tách biệt khỏi thread management (Thread)
- Reusability: Một Runnable có thể được dùng cho nhiều threads
- Functional interface: Dùng được lambda expression
Cách 3: Implements Callable<V> với Future
Callable khác Runnable ở chỗ:
- Có thể return kết quả
- Có thể throw checked exception
import java.util.concurrent.*;
public class CallableDemo {
public static void main(String[] args) throws Exception {
// Tạo Callable task
Callable<Integer> task = () -> {
System.out.println("Calculating sum...");
Thread.sleep(2000);
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum; // Trả về kết quả
};
// Cần ExecutorService để chạy Callable
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(task);
System.out.println("Task submitted. Doing other work...");
// Lấy kết quả (blocking call)
Integer result = future.get(); // Chờ cho đến khi task hoàn thành
System.out.println("Sum: " + result);
executor.shutdown();
}
}
Output:
Task submitted. Doing other work...
Calculating sum...
Sum: 5050
get(): Chờ và lấy kết quả (blocking)get(timeout, unit): Chờ tối đa timeoutisDone(): Kiểm tra task đã hoàn thành chưacancel(mayInterrupt): Hủy task
So sánh Thread vs Runnable vs Callable
| Đặc điểm | Thread | Runnable | Callable |
|---|---|---|---|
| Cách khai báo | extends Thread | implements Runnable | implements Callable<V> |
| Method | run() | run() | call() |
| Return value | Không | Không | Có (generic type) |
| Exception | Không throws | Không throws | Có thể throws Exception |
| Sử dụng | thread.start() | new Thread(runnable).start() | executor.submit(callable) |
| Kết quả | Không thể lấy trực tiếp | Không thể lấy trực tiếp | Future<V> |
| Inheritance | Không extends được class khác | Có thể extends class khác | Có thể extends class khác |
Thread Methods quan trọng
1. start() vs run()
Thread t = new Thread(() -> System.out.println("Hello from thread"));
// ĐÚNG: Tạo thread mới
t.start();
// SAI: Chỉ gọi method run() trong main thread, không tạo thread mới!
t.run();
start(): Tạo thread mới và gọirun()trong thread đórun(): Thực thi code trong thread hiện tại (không tạo thread mới)
start() hoạt động như thế nào?
Khi gọi start(), JVM thực hiện các bước sau:
- Kiểm tra thread state (nếu đã started →
IllegalThreadStateException) - Đăng ký thread với thread group
- Gọi native method
start0()để yêu cầu OS tạo thread mới - OS scheduler khởi tạo thread và chuyển từ NEW → RUNNABLE state
- Khi thread được scheduled, JVM gọi method
run()trong thread context mới đó
Đó là lý do tại sao gọi run() trực tiếp chỉ thực thi trong current thread — không có bước 3, 4, 5!
2. sleep(milliseconds)
Tạm dừng thread trong khoảng thời gian nhất định:
public class SleepDemo {
public static void main(String[] args) {
Thread t = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println("Count: " + i);
try {
Thread.sleep(1000); // Ngủ 1 giây
} catch (InterruptedException e) {
System.out.println("Thread interrupted during sleep");
}
}
});
t.start();
}
}
Thread.sleep() là static method, nó luôn tạm dừng current thread (thread đang gọi nó), không phải thread object.
Quan trọng: sleep() KHÔNG giải phóng lock nếu thread đang trong synchronized block. Khác với wait() (sẽ học ở bài Synchronization).
3. join()
Chờ thread khác hoàn thành:
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
System.out.println("Worker: Starting work...");
try {
Thread.sleep(3000); // Giả lập công việc mất 3 giây
} catch (InterruptedException e) { }
System.out.println("Worker: Work completed!");
});
worker.start();
System.out.println("Main: Waiting for worker to finish...");
worker.join(); // Main thread chờ worker thread kết thúc
System.out.println("Main: Worker finished. Continuing...");
}
}
Output:
Main: Waiting for worker to finish...
Worker: Starting work...
Worker: Work completed!
Main: Worker finished. Continuing...
join() với timeout:
worker.join(2000); // Chờ tối đa 2 giây
if (worker.isAlive()) {
System.out.println("Worker still running after 2 seconds");
}
4. interrupt() - Cơ chế hợp tác (cooperative)
Gửi tín hiệu interrupt tới thread:
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) {
System.out.println("Working... " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("Thread was interrupted!");
// Clean up resources
return;
}
});
t.start();
Thread.sleep(3000); // Chờ 3 giây
System.out.println("Main: Interrupting thread...");
t.interrupt(); // Gửi interrupt signal
}
}
Interrupt là cơ chế hợp tác (cooperative mechanism):
Interrupt KHÔNG force stop thread, nó chỉ đặt một interrupt flag lên thread. Thread phải tự kiểm tra và phản hồi. Có 2 cách phản hồi interrupt:
1. InterruptedException (blocking methods):
- Các method như
sleep(),wait(),join()tự động kiểm tra interrupt flag - Nếu flag được set, chúng throw InterruptedException và clear flag
- Thread phải catch exception và quyết định xử lý (return, cleanup, re-interrupt)
2. Thread.interrupted() / isInterrupted() (non-blocking code):
- Dùng cho code không blocking (vòng lặp, tính toán)
Thread.interrupted(): STATIC method, check flag của current thread và CLEAR flagthread.isInterrupted(): INSTANCE method, check flag KHÔNG clear
// Cách 1: Thread.interrupted() (clears flag)
public void run() {
while (!Thread.interrupted()) { // Kiểm tra và clear flag
// Do work
processData();
}
System.out.println("Thread stopped gracefully");
}
// Cách 2: isInterrupted() (doesn't clear)
public void run() {
while (!Thread.currentThread().isInterrupted()) {
processData();
if (Thread.currentThread().isInterrupted()) {
cleanup();
break;
}
}
}
// Cách 3: Handle exception và re-interrupt nếu cần propagate
public void run() {
try {
while (true) {
doWork();
Thread.sleep(100);
}
} catch (InterruptedException e) {
// Re-set interrupt flag nếu cần propagate lên caller
Thread.currentThread().interrupt();
cleanup();
}
}
- InterruptedException clear flag: Sau khi throw, flag = false
- Thread.interrupted() clear flag: Sau khi gọi, flag = false
- isInterrupted() KHÔNG clear flag: Flag vẫn còn sau khi check
- Interrupt không làm thread dừng ngay lập tức — thread phải cooperative
5. isAlive()
Kiểm tra thread còn đang chạy không:
Thread t = new Thread(() -> {
try { Thread.sleep(2000); }
catch (InterruptedException e) { }
});
System.out.println("Before start: " + t.isAlive()); // false
t.start();
System.out.println("After start: " + t.isAlive()); // true
Thread.sleep(3000);
System.out.println("After finish: " + t.isAlive()); // false
Thread Priority
Thread có độ ưu tiên từ 1 (thấp nhất) đến 10 (cao nhất):
public class PriorityDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Low priority: " + i);
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("High priority: " + i);
}
});
t1.setPriority(Thread.MIN_PRIORITY); // 1
t2.setPriority(Thread.MAX_PRIORITY); // 10
t1.start();
t2.start();
}
}
Constants:
Thread.MIN_PRIORITY= 1Thread.NORM_PRIORITY= 5 (default)Thread.MAX_PRIORITY= 10
Priority chỉ là gợi ý cho thread scheduler. Không đảm bảo high priority thread sẽ chạy trước. Phụ thuộc vào OS và JVM implementation.
Daemon Threads vs User Threads
User Threads (default)
- JVM chờ tất cả user threads kết thúc mới thoát
- Main thread là user thread
Daemon Threads (nền/hậu cảnh)
Daemon threads là background threads phục vụ cho user threads. Ví dụ:
- Garbage Collector: Thu gom rác
- Finalizer thread: Thực thi finalizers
- Signal dispatcher: Xử lý OS signals
- Reference Handler: Quản lý reference objects
Đặc điểm quan trọng:
- JVM KHÔNG CHỜ daemon threads hoàn thành khi tất cả user threads đã kết thúc
- Daemon threads bị KILL ĐỘT NGỘT khi JVM shutdown — có thể dừng giữa chừng một operation
public class DaemonDemo {
public static void main(String[] args) {
Thread daemon = new Thread(() -> {
while (true) {
System.out.println("Daemon running...");
try { Thread.sleep(500); }
catch (InterruptedException e) { }
}
});
daemon.setDaemon(true); // Phải set TRƯỚC khi start()
daemon.start();
// Main thread (user thread) ngủ 2 giây rồi kết thúc
try { Thread.sleep(2000); }
catch (InterruptedException e) { }
System.out.println("Main thread ending...");
// JVM thoát, daemon thread bị kill ngay cả khi đang trong vòng lặp
}
}
setDaemon(true) PHẢI được gọi TRƯỚC start(). Nếu gọi sau → IllegalThreadStateException
Thread t = new Thread(() -> doWork());
t.start();
t.setDaemon(true); // ❌ RUNTIME EXCEPTION!
KHÔNG dùng daemon threads cho:
- File I/O operations (file có thể bị corrupt khi kill giữa chừng)
- Database transactions (có thể mất dữ liệu)
- Network operations (connection không đóng đúng cách)
- Critical cleanup tasks
Daemon threads nên chỉ dùng cho non-critical background tasks như monitoring, logging, cache refresh.
- Monitoring threads (health checks)
- Background logging threads
- Periodic cache refresh
- Session cleanup schedulers
- Metrics collection
Virtual Threads (Java 21+)
Virtual threads là một trong những tính năng quan trọng nhất của Project Loom (Java 21+).
Vấn đề với Platform Threads truyền thống:
- Mỗi platform thread (traditional Java thread) map 1:1 với OS thread
- OS threads rất nặng (≈2MB stack mỗi thread)
- Giới hạn thực tế: ~10,000 platform threads trên một máy
- Blocking I/O lãng phí CPU vì thread phải chờ
Virtual Threads giải quyết:
- Lightweight: ~1KB stack per virtual thread
- Managed by JVM: JVM scheduler, không phải OS
- Có thể tạo hàng triệu virtual threads (tested với 1 million+)
- Không cần thread pooling — tạo thread per request là OK!
// Platform thread (traditional)
Thread platformThread = Thread.ofPlatform()
.name("platform-worker")
.start(() -> doWork());
// Virtual thread (Java 21+)
Thread virtualThread = Thread.ofVirtual()
.name("virtual-worker")
.start(() -> doWork());
// ExecutorService với virtual threads
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 100_000; i++) {
executor.submit(() -> handleRequest());
}
} // Auto shutdown khi close
// Hoặc tạo đơn giản
Thread.startVirtualThread(() -> processData());
Sự khác biệt chính:
| Platform Thread | Virtual Thread | |
|---|---|---|
| Weight | ~2MB | ~1KB |
| Managed by | OS | JVM |
| Limit | ~10K | Millions |
| Pooling | Cần pool (expensive) | Không cần (cheap) |
| Best for | CPU-bound tasks | I/O-bound tasks |
Giới hạn của Virtual Threads:
- KHÔNG phù hợp với CPU-bound tasks: Virtual threads vẫn chạy trên platform threads (carrier threads)
- Tránh
synchronized: DùngReentrantLockthay thế (synchronized pin carrier thread) - ThreadLocal có cost: Mỗi virtual thread có riêng ThreadLocal → memory overhead nếu dùng nhiều
// ❌ Tránh synchronized với virtual threads
synchronized(lock) {
// Pin carrier thread!
}
// ✅ Dùng ReentrantLock
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// Không pin carrier thread
} finally {
lock.unlock();
}
Virtual threads là game-changer cho high-throughput I/O applications (web servers, microservices)!
Thread Naming
public class ThreadNamingDemo {
public static void main(String[] args) {
// Cách 1: Đặt tên qua constructor
Thread t1 = new Thread(() -> {
System.out.println("Running in: " + Thread.currentThread().getName());
}, "Worker-1");
// Cách 2: Dùng setName()
Thread t2 = new Thread(() -> {
System.out.println("Running in: " + Thread.currentThread().getName());
});
t2.setName("Worker-2");
t1.start();
t2.start();
// Current thread
System.out.println("Main thread: " + Thread.currentThread().getName());
}
}
Output:
Main thread: main
Running in: Worker-1
Running in: Worker-2
Ví dụ thực hành: Download Manager
import java.util.ArrayList;
import java.util.List;
class FileDownloader implements Runnable {
private String fileName;
private int sizeInMB;
public FileDownloader(String fileName, int sizeInMB) {
this.fileName = fileName;
this.sizeInMB = sizeInMB;
}
@Override
public void run() {
System.out.println("Downloading " + fileName + " (" + sizeInMB + " MB)...");
for (int i = 10; i <= 100; i += 10) {
try {
Thread.sleep(500); // Giả lập thời gian download
} catch (InterruptedException e) {
System.out.println(fileName + " download interrupted");
return;
}
System.out.println(fileName + ": " + i + "% complete");
}
System.out.println(fileName + " downloaded successfully!");
}
}
public class DownloadManager {
public static void main(String[] args) throws InterruptedException {
List<Thread> downloads = new ArrayList<>();
// Tạo 3 download tasks
String[] files = {"video.mp4", "music.mp3", "document.pdf"};
int[] sizes = {100, 50, 20};
for (int i = 0; i < files.length; i++) {
Thread t = new Thread(
new FileDownloader(files[i], sizes[i]),
"Downloader-" + (i + 1)
);
downloads.add(t);
t.start();
}
// Chờ tất cả downloads hoàn thành
for (Thread t : downloads) {
t.join();
}
System.out.println("\n✓ All downloads completed!");
}
}
Best Practices
- Prefer Runnable over Thread: Linh hoạt hơn, dùng được lambda
- Always handle InterruptedException: Không bỏ qua exception
- Name your threads: Dễ debug
- Use join() khi cần chờ: Đừng dùng
while(thread.isAlive()) - Don't call run() directly: Luôn dùng
start() - Set daemon before start():
setDaemon()phải gọi trướcstart()
Bài tập
Bài 1: Thread Basics
Tạo 3 threads:
- Thread 1: In số lẻ từ 1-99
- Thread 2: In số chẵn từ 2-100
- Thread 3: In số chia hết cho 5 từ 5-100
Chạy đồng thời và quan sát output.
Bài 2: Callable và Future
Viết 3 Callable tasks:
- Tính tổng 1 đến 1000
- Tính tổng bình phương 1 đến 100
- Tính giai thừa của 10
Chạy song song và in kết quả.
Bài 3: Download Manager nâng cao
Mở rộng DownloadManager với:
- Progress bar cho mỗi download
- Khả năng pause/resume (dùng interrupt)
- Giới hạn tối đa 2 downloads đồng thời
- Tính tổng thời gian download
Những điểm quan trọng cho kỳ thi:
-
start() called twice → IllegalThreadStateException
Thread t = new Thread(() -> {});
t.start();
t.start(); // ❌ IllegalThreadStateException -
sleep() KHÔNG giải phóng lock (synchronized)
- Thread.sleep() vẫn giữ lock nếu đang trong synchronized block
- Khác với wait() (sẽ học ở bài Synchronization)
-
run() vs start()
run(): Thực thi trong CALLER thread (không tạo thread mới)start(): Tạo NEW thread và gọi run() trong đó
-
Thread.yield() chỉ là hint
- Không đảm bảo thread khác sẽ chạy
- Scheduler có thể ignore
-
Runnable vs Callable
- Runnable:
void run(), không throws exception - Callable:
V call() throws Exception, trả về kết quả qua Future
- Runnable:
-
setDaemon() must be before start()
thread.start();
thread.setDaemon(true); // ❌ IllegalThreadStateException -
Thread.interrupted() vs isInterrupted()
Thread.interrupted(): STATIC, check current thread, CLEAR flagthread.isInterrupted(): INSTANCE, KHÔNG clear flag
-
Thread priority không đảm bảo thứ tự thực thi
- Chỉ là hint cho scheduler
- Phụ thuộc OS và JVM implementation
-
InterruptedException clears interrupt flag
- Sau khi throw, flag = false
- Nếu cần propagate, phải re-interrupt:
Thread.currentThread().interrupt()
Tóm tắt
- 3 cách tạo thread: extends Thread, implements Runnable (recommended), implements Callable
- start() vs run():
start()gọi native start0() để tạo OS thread,run()chỉ gọi method - Thread methods:
sleep(),join(),interrupt(),isAlive() - Interrupt: Cơ chế hợp tác (cooperative), set flag, thread tự check và phản hồi
- Priority: 1-10, chỉ là gợi ý cho scheduler (preemptive - ưu tiên/chiếm quyền)
- Daemon threads: Background threads, JVM không chờ, bị kill đột ngột khi shutdown
- Virtual Threads (Java 21+): Lightweight, millions possible, dùng cho I/O-bound tasks
- Thread naming: Giúp debug dễ dàng
Bài tiếp theo sẽ học về Synchronization - cách giải quyết race conditions!
Đọc thêm
Official Documentation:
Books:
- Effective Java (3rd Edition) by Joshua Bloch
- Item 81: Prefer concurrency utilities to wait and notify
- Item 80: Prefer executors to threads
- Java Concurrency in Practice by Brian Goetz (Chapter 5: Building Blocks)
JLS (Java Language Specification):
Related lessons:
- Synchronization — Giải quyết race conditions với locks
- Executor Framework — Thread pool và high-level concurrency