Hôm nay chúng ta sẽ ngó sang một chút về Java, ví dụ một câu hỏi phỏng vấn mà bạn được hỏi như sau: yêu cầu cần ba luồng thực hiện tuần tự, có những cách triển khai nào để có thể đạt được nó.
Bài toán sẽ là ta có ba luồng: Luồng 1, Luồng 2, Luồng 3, yêu cầu là luồng 1 sẽ được xử lý trước, sau đó tới luồng 2, và cuối cùng là luồng 3. Chúng ta sẽ có một vài cách như sau:
Sử dụng phương thức Join
import java.util.Objects;
public class ThreadMain {
public static void main(String[] args) {
var t1 = new Thread(new Work(null));
var t2 = new Thread(new Work(t1));
var t3 = new Thread(new Work(t2));
t1.start();
t2.start();
t3.start();
}
static class Work implements Runnable {
private Thread beforeThread;
public Work(Thread beforeThread) {
this.beforeThread = beforeThread;
}
@Override
public void run() {
if (Objects.nonNull(beforeThread)) {
try {
beforeThread.join();
System.out.println("Thread start : " + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println("Thread start : " + Thread.currentThread().getName());
}
}
}
}
Đọc đoạn code trên các bạn có thể hiểu được phần nào logic, truyền tham số vào trong hàm khởi tạo của các luồng 1, 2, 3. Lúc này luồng 1 sẽ được xử lý trước, luồng 2 join với luồng 1 thì sẽ được xử lý tiếp tục, và cuối cùng luồng 3 join với luồng 2 thì sẽ được xử lý cuối cùng.
Start ứng dụng lên và xem kết quả:

Sử dụng CountDown Latch
import java.util.concurrent.CountDownLatch;
public class CountDownLatchMain {
public static void main(String[] args) {
var countDownLatchOne = new CountDownLatch(0);
var countDownLatchTwo = new CountDownLatch(1);
var countDownLatchThree = new CountDownLatch(1);
var t1 = new Thread(new Work(countDownLatchOne, countDownLatchTwo));
var t2 = new Thread(new Work(countDownLatchTwo, countDownLatchThree));
var t3 = new Thread(new Work(countDownLatchThree, countDownLatchThree));
t1.start();
t2.start();
t3.start();
}
static class Work implements Runnable {
CountDownLatch cOne;
CountDownLatch cTwo;
public Work(CountDownLatch cOne, CountDownLatch cTwo) {
this.cOne = cOne;
this.cTwo = cTwo;
}
@Override
public void run() {
try {
cOne.await();
System.out.println("Thread using countdown latch start : " + Thread.currentThread().getName());
cTwo.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Ok, bây giờ start ứng dụng lên và xem kết quả, sau đó sẽ đi chi tiết về cách thực thi của nó:

Trong hàm main(), ba đối tượng CountDownLatch được tạo với các giá trị ban đầu là 0, 1 và 1.
countDownLatchOneđược sử dụng để đánh dấu cho tất cả các thread đã sẵn sàng để bắt đầucountDownLatchTwođược sử dụng để bắt đầut2sau khit1đã kết thúccountDownLatchThreeđược sử dụng để bắt đầut3sau khit2đã kết thúc
Ba đối tượng Thread được tạo với ba đối tượng Work tương ứng được truyền vào. Sau đó, ba thread được bắt đầu bằng cách gọi phương thức start().
Ở trong class Work, việc thực thi thể hiện như sau:
- Class
Worklà một lớp triển khaiRunnablevới mục đích thực hiện một số công việc được thực thi trong một thread riêng biệt.
- Lớp
Workcó hai tham số đầu vào, đó là hai đối tượng của lớpCountDownLatch,cOnevàcTwo.
- Trong phương thức
run()của lớpWork, trước khi bắt đầu thực thi công việc, thread sẽ gọi phương thứcawait()trêncOne, đợi đến khicOnegiảm giá trị đếm ngược xuống 0.
- Sau đó, thread sẽ in ra một thông báo để xác nhận rằng nó đã bắt đầu thực hiện công việc của nó. Đây là điểm đánh dấu cho việc
t1đã hoàn thành công việc của mình.
- Cuối cùng, thread sẽ gọi phương thức
countDown()trêncTwođể giảm giá trị đếm ngược củacTwoxuống 0. Việc giảm giá trị đếm ngược củacTwosẽ đánh dấu cho việct2được bắt đầu thực thi.
- Quá trình tương tự sẽ tiếp diễn cho
t2vàt3theo đúng thứ tự đưa vào. Tức là,t2sẽ đợi đến khicTwogiảm giá trị đếm ngược xuống 0 bằng cách gọi phương thứcawait()trêncTwo, sau đó in ra một thông báo xác nhận và cuối cùng giảm giá trị đếm ngược củacThreexuống 0 bằng cách gọi phương thứccountDown()trêncThree. - Tương tự,
t3sẽ đợi đến khicThreegiảm giá trị đếm ngược xuống 0 bằng cách gọi phương thứcawait()trêncThree, in ra một thông báo xác nhận và cuối cùng cũng kết thúc thực thi của nó.
Sử dụng ExecutorService
Chúng ta có thể triển khai một nhóm luồng đơn bằng ExecutorService, tuy nhiên chỉ sử dụng đơn luồng, vì nếu triển khai nhóm nhiều luồng thì sẽ không đảm bảo được thứ tự của T1, T2, T3.
import java.util.concurrent.Executors;
public class ExecutorServiceMain {
public static void main(String[] args) {
Thread t1 = new Thread(() -> System.out.println("Thread Start : " + Thread.currentThread().getName() + " 1"));
Thread t2 = new Thread(() -> System.out.println("Thread Start : " + Thread.currentThread().getName() + " 2"));
Thread t3 = new Thread(() -> System.out.println("Thread Start : " + Thread.currentThread().getName() + " 3"));
// var executor = Executors.newFixedThreadPool(1);
var executor = Executors.newSingleThreadExecutor();
executor.submit(t1);
executor.submit(t2);
executor.submit(t3);
executor.shutdown();
}
}

Trên đây là một vài cách xử lý để có thể giải quyết bài toán về thứ tự xử lý của các luồng, chúc các bạn học tốt.
Xem thêm một số bài viết nổi bật:
- Crack Intellij IDEA Ultimate version 2022
- Phỏng vấn dạo kĩ sư phần mềm 2023
- Kĩ năng quản lý căng thẳng cho Developer
- Bạn không giỏi lắng nghe như bạn nghĩ đâu
- Using Amazon CodeWhisperer for fast coding
- How to using Cassandra with Spring boot
- Biết sử dụng git cherry-pick để làm việc hiệu quả hơn
- Git stash giúp bạn trở nên chuyên nghiệp như thế nào?
- Cài đặt Hazelcast trên server Centos 7
- 13 Plugin không thể thiếu khi làm việc với IntellIJ IDEA
- Shortcut Intellij hữu ích để làm việc được hiệu quả hơn
- How to build Rate Limit with Hazelcast and Spring Boot
- Hazelcast Distributed Cache with Spring Boot
- How to build Cron Job for multiple instances with ShedLock
- Distributed Lock with Hazelcast and Spring