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 đầut2
sau khit1
đã kết thúccountDownLatchThree
được sử dụng để bắt đầut3
sau 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
Work
là một lớp triển khaiRunnable
vớ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
Work
có hai tham số đầu vào, đó là hai đối tượng của lớpCountDownLatch
,cOne
và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 khicOne
giả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ủacTwo
xuống 0. Việc giảm giá trị đếm ngược củacTwo
sẽ đá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
t2
vàt3
theo đúng thứ tự đưa vào. Tức là,t2
sẽ đợi đến khicTwo
giả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ủacThree
xuống 0 bằng cách gọi phương thứccountDown()
trêncThree
. - Tương tự,
t3
sẽ đợi đến khicThree
giả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