Mục lục
Trong lập trình đa luồng, đồng bộ hóa là một trong những công việc mà các lập trình viên phải thực hiện để tránh xảy ra các kết quả không đáng có hoặc có thể gây ra các vấn đề nguyên trọng như DeadLock etc.
Để đảm bảo một thread phải chờ cho đến khi các thread khác thực thi xong thì mới được chạy, thì ở những bài trước mình đã giới thiệu như là sử dụng vòng lặp while kết hợp isAlive() method, join() method. Trong bài viết này chúng ta sẽ biết thêm một cách mới là sử dụng wait() kết hợp với notify() và notifyAll().
Guarded block trong java
Guareded block là một khối mã code được kiểm tra bởi một điều kiện cụ thể trước khi chúng được thực thi. Để tạo ra một Guarded block chúng ta có thể sử dụng:
- wait() – Để dừng một thread
- notify() – Đánh thức thread
- notifyAll() – Đánh thức tất cả thread
Thread wait()
Khi wait() method được gọi, thread hiện tại sẽ rơi vào trạng thái chờ cho đến khi một thread khác gọi notify() hay notifyAll() trên cùng một object. Vì thế wait() method phải được đặt trong monitor object (là một khối code truy cập vào các object được sử dụng bởi nhiều thread, chúng được bảo vệ bởi synchronized để đảm bảo rằng chỉ có 1 thread truy cập vào object tại cùng một thời điểm).
- synchronized instance method
- synchronized object
- synchronized static method
Chúng ta có nhiều biến thể khác của wait method như:
- wait(long timeout) – Thread sẽ chờ một khoảng thời gian timeout(millis) cụ thể sau đó nó sẽ được đánh thức. Thread có thể thức dậy sớm hơn khi notify() hoặc notifyAll() được gọi trong khoảng thời gian timeout.
- wait(long timeout) – Tương tự wait(long timeout) chỉ được chỉ định thêm một khoảng nanos.
Thread notify()
Tất các các thread đang chờ trên cùng một object monitor sẽ được đánh thức bất kỳ khi notify() method được gọi, sẽ không có một thứ tự cụ thể cho các thread được đánh thức. Chúng ta sẽ không thể chọn chính xác chủ đề nào sẽ được đánh thức khi gọi notify() mà chúng ta phải thêm một số điều kiện để xác định hoặc chỉ có 2 thread tham gia vào thì quá dễ dàng.
Thread notifyAll()
Tất các các thread đang chờ trên cùng một object monitor sẽ được đánh thức khi notifyAll() được gọi. Nhưng hãy cẩn thận vì một notifyAll() sẽ gây mất kiểm soát đấy, nên kết hợp thêm các điều kiện để xác định cụ thể những thread nào sẽ được thực thi tiếp.
Ví dụ wait(), notify(), notifyAll()
Thông thừng thì notify() chỉ được dùng khi có 2 thread ngoài ra thì nên sử dụng notifyAll() kết hợp với các điều kiện để xác định các thread được đánh thức. Vì notify() không có tính ổn định khi mà nó đánh thức một thread bất kỳ.
class Data { private String packet; private boolean transfer = true; public synchronized void send(String packet) { while(!transfer) { try { wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } transfer = false; this.packet = packet; notifyAll(); } public synchronized String receive() { while (transfer) { try { wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } transfer = true; notifyAll(); return packet; } }
Chúng ta có 1 class Data đại diện cho một gói dữ liệu sẽ được chuyển đi và nhận về bởi các thread. Để ý
while(!transfer) { try { wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
Mình có thể sử dụng if thay cho while.
if(!transfer) { try { wait(3); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
Thế nhưng java sẽ khuyên chúng ta sử dụng while thay cho if vì trong một số trường hợp cụ thể sử dụng wait(long timeout) chẳng hạn thì thread sẽ được đánh thức sau một thời gian, hoặc bị các thread khác đánh thức không mong muốn trong khi mục đích của chúng ta là muốn thread chờ cho đến khi một điều kiện được thỏa. Vì vậy nên sử dụng while bao bọc wait() trong hầu hết các trường hợp.
Chúng ta có một class Sender class sẽ gửi các gói Data đi cho các thread khác
class Sender implements Runnable { private Data data; public Sender(Data data) { this.data = data; } public void run() { String packets[] = { "First packet", "Second packet", "Third packet", "Fourth packet", "End" }; for (String packet : packets) { data.send(packet); // Thread.sleep() to mimic heavy server-side processing try { Thread.sleep(ThreadLocalRandom.current().nextInt(1000, 5000)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } }
Receiver class nhận dữ liệu Data gửi từ các thread khác đến. Sender class sẽ ngưng nhận dữ liệu khi gói tin là một chuỗi End.
class Receiver implements Runnable { private Data load; public Receiver(Data load) { this.load = load; } public void run() { for(String receivedMessage = load.receive(); !"End".equals(receivedMessage); receivedMessage = load.receive()) { System.out.println(receivedMessage); // ... try { Thread.sleep(ThreadLocalRandom.current().nextInt(1000, 5000)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } }
class main{ public static void main(String[] args) { Data data = new Data(); Thread sender = new Thread(new Sender(data)); Thread receiver = new Thread(new Receiver(data)); sender.start(); receiver.start(); } }
Output
First packet
Second packet
Third packet
Fourth packet
Tóm lược
Vậy là chúng ta đã tìm hiểu xong về notify(), notifyAll(), wait() trong lập trình multithreading. Biết thêm một cách để xử lý đồng bộ hóa trong java. Và nhớ né notify() ra nhé, thanh niên này chơi bậy lắm
Nguồn tham khảo