1. Truyền tham số vào luồng
Khi bạn khởi chạy một luồng, thường bạn muốn truyền cho nó một “công việc”: tên tệp, khoảng số hoặc lời chào. Một luồng không có tham số — giống như người giao hàng không có địa chỉ: chạy khắp thành phố nhưng không biết giao pizza đến đâu.
Làm như thế nào?
Trong Java, luồng thường được tạo theo hai cách:
- Kế thừa từ lớp Thread
- Triển khai interface Runnable (hoặc dùng biểu thức lambda)
Truyền tham số thường được thực hiện qua constructor của lớp triển khai Runnable. Cách này đơn giản, an toàn và dễ hiểu. Hãy khai báo các trường là final — bạn sẽ bảo vệ các tham số khỏi bị thay đổi từ các luồng khác. Dùng setter/trường public dễ dẫn đến vấn đề đồng bộ hóa.
Ví dụ 1: Luồng nhận tham số qua constructor
Cùng cải tiến ứng dụng học tập: xử lý đơn hàng của người dùng trong một luồng riêng.
public class OrderProcessor implements Runnable {
private final String orderId;
public OrderProcessor(String orderId) {
this.orderId = orderId;
}
@Override
public void run() {
System.out.println("Đang xử lý đơn hàng: " + orderId + " trong luồng " + Thread.currentThread().getName());
// Ở đây có thể là xử lý lâu...
}
}
Tạo và khởi chạy các luồng:
public class Main {
public static void main(String[] args) {
Thread thread1 = new Thread(new OrderProcessor("ORDER-001"));
Thread thread2 = new Thread(new OrderProcessor("ORDER-002"));
thread1.start();
thread2.start();
}
}
Kết quả mong đợi:
Đang xử lý đơn hàng: ORDER-001 trong luồng Thread-0
Đang xử lý đơn hàng: ORDER-002 trong luồng Thread-1
Ví dụ 2: Luồng nhận tham số qua biểu thức lambda (Java 8+)
Nếu tác vụ đơn giản, có thể không cần lớp riêng.
public class Main {
public static void main(String[] args) {
String user1 = "Vasya";
String user2 = "Katya";
Thread thread1 = new Thread(() -> {
System.out.println("Xin chào, " + user1 + " từ " + Thread.currentThread().getName());
});
Thread thread2 = new Thread(() -> {
System.out.println("Xin chào, " + user2 + " từ " + Thread.currentThread().getName());
});
thread1.start();
thread2.start();
}
}
Vì sao không nên dùng setter/getter để truyền tham số?
Nếu bạn truyền tham số qua setter hoặc trường public, có rủi ro luồng khác sẽ thay đổi giá trị ngay trong lúc thực thi. Điều này có thể dẫn tới lỗi khó lần ra. Tốt hơn hãy đặt các trường là final và truyền giá trị qua constructor.
2. Mức ưu tiên của luồng
Trong Java, mỗi luồng có một mức ưu tiên — số nguyên từ 1 đến 10, cho bộ lập lịch (scheduler) biết luồng này “quan trọng” thế nào so với các luồng khác. Mặc định mọi luồng có mức ưu tiên 5 (Thread.NORM_PRIORITY).
Lưu ý: mức ưu tiên chỉ là một “gợi ý” cho hệ điều hành. Không có gì đảm bảo rằng luồng với mức ưu tiên 10 sẽ chạy nhanh hơn luồng có mức ưu tiên 1. Tất cả phụ thuộc vào hệ điều hành, cấu hình và tải hiện tại.
Cách thiết lập mức ưu tiên cho luồng?
Lớp Thread có các phương thức:
- setPriority(int newPriority)
- getPriority()
Và ba hằng số chuẩn:
- Thread.MIN_PRIORITY (1)
- Thread.NORM_PRIORITY (5)
- Thread.MAX_PRIORITY (10)
Ví dụ 3: Thiết lập mức ưu tiên cho luồng
public class PriorityDemo {
public static void main(String[] args) {
Runnable task = () -> {
System.out.println("Luồng " + Thread.currentThread().getName() +
" với mức ưu tiên " + Thread.currentThread().getPriority());
};
Thread low = new Thread(task, "LowPriority");
Thread norm = new Thread(task, "NormalPriority");
Thread high = new Thread(task, "HighPriority");
low.setPriority(Thread.MIN_PRIORITY); // 1
norm.setPriority(Thread.NORM_PRIORITY); // 5
high.setPriority(Thread.MAX_PRIORITY); // 10
low.start();
norm.start();
high.start();
}
}
Kết quả mong đợi (thứ tự dòng không được đảm bảo!):
Luồng LowPriority với mức ưu tiên 1
Luồng HighPriority với mức ưu tiên 10
Luồng NormalPriority với mức ưu tiên 5
Mức ưu tiên có ảnh hưởng đến thứ tự thực thi không?
Trong đa số trường hợp — không. Mức ưu tiên có thể ảnh hưởng đến lượng thời gian CPU cấp cho luồng, nhưng không đảm bảo thứ tự bắt đầu hay kết thúc. Đừng xây dựng logic chương trình dựa vào mức ưu tiên — hãy dùng chúng như “gợi ý nhẹ”, ví dụ để luồng nền không ảnh hưởng luồng chính.
Bảng: Các hằng mức ưu tiên của luồng
| Hằng số | Giá trị | Mô tả |
|---|---|---|
|
1 | Mức ưu tiên thấp nhất |
|
5 | Mức ưu tiên thông thường |
|
10 | Mức ưu tiên cao nhất |
3. Đặt tên cho luồng
Tên của luồng là “biệt danh” trong thế giới đa nhiệm. Khi bạn có hàng chục luồng, việc debug sẽ dễ hơn nếu thay vì "Thread-7" trong log bạn thấy "FileUploader-1". Điều này đặc biệt quan trọng khi phân tích log và tìm lỗi.
Cách đặt tên cho luồng?
Có thể chỉ định tên ngay trong constructor của Thread hoặc đặt bằng phương thức setName. Để lấy tên hiện tại — getName.
Thread t = new Thread(() -> {
System.out.println("Tôi đang làm việc!");
}, "MyThreadName");
t.start();
Ví dụ 4: Các luồng có tên
public class NamedThreads {
public static void main(String[] args) {
Thread threadA = new Thread(() -> {
System.out.println("Tôi là luồng: " + Thread.currentThread().getName());
}, "Downloader");
Thread threadB = new Thread(() -> {
System.out.println("Tôi là luồng: " + Thread.currentThread().getName());
});
threadB.setName("Uploader");
threadA.start();
threadB.start();
}
}
Kết quả mong đợi:
Tôi là luồng: Downloader
Tôi là luồng: Uploader
4. Thực hành: nhiều luồng với tham số, mức ưu tiên và tên khác nhau
Kết hợp tất cả trong một ví dụ: mỗi đơn hàng là một luồng riêng với tên và mức ưu tiên của nó.
public class OrderProcessor implements Runnable {
private final String orderId;
private final int processingTimeMs;
public OrderProcessor(String orderId, int processingTimeMs) {
this.orderId = orderId;
this.processingTimeMs = processingTimeMs;
}
@Override
public void run() {
System.out.println("[" + Thread.currentThread().getName() + "] Bắt đầu xử lý đơn hàng " + orderId +
" (mức ưu tiên " + Thread.currentThread().getPriority() + ")");
try {
Thread.sleep(processingTimeMs); // mô phỏng công việc
} catch (InterruptedException e) {
System.out.println("[" + Thread.currentThread().getName() + "] Đã bị gián đoạn!");
}
System.out.println("[" + Thread.currentThread().getName() + "] Hoàn tất xử lý đơn hàng " + orderId);
}
public static void main(String[] args) {
Thread fastOrder = new Thread(new OrderProcessor("FAST-ORDER", 500), "FastOrderThread");
Thread normalOrder = new Thread(new OrderProcessor("NORMAL-ORDER", 1000), "NormalOrderThread");
Thread slowOrder = new Thread(new OrderProcessor("SLOW-ORDER", 2000), "SlowOrderThread");
fastOrder.setPriority(Thread.MAX_PRIORITY);
normalOrder.setPriority(Thread.NORM_PRIORITY);
slowOrder.setPriority(Thread.MIN_PRIORITY);
fastOrder.start();
normalOrder.start();
slowOrder.start();
}
}
Sẽ thấy gì:
- Mỗi luồng in ra đơn hàng nó đang xử lý, tên của nó và mức ưu tiên.
- Thời gian xử lý được mô phỏng bằng Thread.sleep.
- Thứ tự hoàn thành của các luồng có thể không trùng với mức ưu tiên.
5. Các lưu ý và đặc điểm quan trọng
Truyền tham số: chỉ qua constructor!
Như vậy an toàn hơn. Khai báo tham số là final — chúng sẽ không thể bị thay đổi sau khi tạo đối tượng, và không luồng nào khác có thể “nhét” giá trị của riêng nó. Điều này rất quan trọng để tránh race condition.
Mức ưu tiên: đừng xây dựng business logic dựa trên chúng
Mức ưu tiên giống như nhờ bồi bàn: “Cho tôi cà phê nhanh hơn được không?”. Đôi khi hiệu quả, đôi khi không. Đó là khuyến nghị cho hệ điều hành, không phải quy tắc thực thi.
Tên luồng: dùng cho debug
Giúp tiết kiệm rất nhiều thời gian khi phân tích log. Hãy tập đặt tên có ý nghĩa ngay từ đầu.
6. Các lỗi thường gặp khi làm việc với tham số và mức ưu tiên của luồng
Lỗi số 1: Truyền tham số qua trường public. Nếu bạn khai báo tham số là trường thông thường và thay đổi chúng sau khi luồng đã khởi chạy, bạn có thể nhận kết quả không thể dự đoán. Hãy dùng trường final và constructor.
Lỗi số 2: Kỳ vọng mức ưu tiên đảm bảo thứ tự. Bạn đặt Thread.MAX_PRIORITY — và nghĩ luồng sẽ luôn chạy trước? Không. Đừng dùng mức ưu tiên để đồng bộ hoặc điều khiển logic.
Lỗi số 3: Luồng không được đặt tên trong log. Khi log chỉ có “Thread-3”, “Thread-7” — rất khó tìm thủ phạm. Hãy đặt tên có ý nghĩa cho luồng bằng constructor hoặc setName.
Lỗi số 4: Dùng cùng một đối tượng Runnable cho nhiều luồng. Nếu đối tượng Runnable giữ trạng thái và bạn truyền nó cho nhiều luồng, chúng sẽ “chia sẻ” tham số — con đường ngắn nhất đến race condition. Hãy tạo một Runnable riêng cho mỗi luồng.
Lỗi số 5: Truyền tham số qua các phương thức set. Nếu bạn gán tham số qua các phương thức set* sau khi đối tượng đã được tạo/luồng đã khởi chạy, giá trị có thể thay đổi “giữa chừng” — tình huống race condition kinh điển.
GO TO FULL VERSION