Bộ khóa synchronized, wait(), và notify() trong Java

Trong Java, không có các phương pháp tương tự như PV operation hay quản lý đồng bộ giữa các tiến trình. Đồng bộ hóa luồng trong Java được thực hiện thông qua từ khóa synchronized. Cần hiểu rằng, từ khóa này hoạt động tương tự như một vùng nhớ bị khóa trong hệ điều hành. Mỗi đối tượng thuộc kiểu Object trong Java đều sở hữu một khóa (lock) riêng. Khi một luồng lấy được khóa này, các luồng khác sẽ không thể truy cập vào vùng nhớ đó, từ đó đảm bảo sự đồng bộ và loại trừ tình trạng cạnh tranh tài nguyên.

Khái niệm này giúp giải thích rõ ràng sự khác biệt giữa synchronized(this) và synchronized(static XXX). Từ khóa synchronized áp dụng cho một khối mã nhất định bằng cách yêu cầu quyền kiểm soát khóa của đối tượng hoặc lớp. Với this, khóa được áp dụng trên từng đối tượng cụ thể. Trong khi đó, với thành viên static, khóa được áp dụng cho toàn bộ lớp, tức là chỉ có một luồng duy nhất có thể truy cập vào bất kỳ thành viên nào của lớp tại cùng một thời điểm.

Để thực hiện việc đánh thức luồng khác, chúng ta cần sử dụng cặp phương thức wait() và notify(). Phương thức Obj.wait() phải được gọi bên trong một khối synchronized(Obj). Khi gọi wait(), luồng hiện tại sẽ nhả khóa đối tượng và đi vào trạng thái ngủ. Chỉ khi một luồng khác gọi notify() trên cùng đối tượng thì luồng ban đầu mới có cơ hội giành lại khóa và tiếp tục thực thi. Lưu ý rằng sau khi gọi notify(), khóa vẫn chưa được trả về ngay mà chỉ được nhả ra khi khối synchronized kết thúc.

Một ví dụ điển hình minh họa cách sử dụng wait() và notify() là bài toán in số đan xen giữa hai luồng:

package com.example.thread;

public class ThreadExample {
    static class SharedResource {
        private int counter = 1;
        private final String resourceLock = "lock";

        public void printEven() {
            synchronized (resourceLock) {
                while (counter <= 10) {
                    if (counter % 2 != 0) {
                        try {
                            resourceLock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        System.out.println(Thread.currentThread().getName() + ": " + counter);
                        counter++;
                        resourceLock.notify();
                    }
                }
            }
        }

        public void printOdd() {
            synchronized (resourceLock) {
                while (counter <= 10) {
                    if (counter % 2 == 0) {
                        try {
                            resourceLock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        System.out.println(Thread.currentThread().getName() + ": " + counter);
                        counter++;
                        resourceLock.notify();
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        SharedResource shared = new SharedResource();

        Thread evenThread = new Thread(() -> shared.printEven());
        Thread oddThread = new Thread(() -> shared.printOdd());

        evenThread.setName("Luồng chẵn");
        oddThread.setName("Luồng lẻ");

        evenThread.start();
        oddThread.start();
    }
}

Kết quả chạy chương trình:

Luồng lẻ: 1
Luồng chẵn: 2
Luồng lẻ: 3
Luồng chẵn: 4
Luồng lẻ: 5
Luồng chẵn: 6
Luồng lẻ: 7
Luồng chẵn: 8
Luồng lẻ: 9
Luồng chẵn: 10

Thẻ: Java Synchronization multithreading

Đăng vào ngày 28 tháng 6 lúc 23:00