Cơ chế xử lý deadlock

Home

Java

Deadlock trong multithreading, Làm sao để tránh Deadlock?

February 11, 2020

Java hỗ trợ lập trình đa luồng (multithreading), khi có nhiều thread chạy đồng thời trong một chương trình, trong một số trường hợp nhất định hoặc do sai xót dẫn đến các thread rơi vào tình trạng chờ mãi mãi. Trong bài viết này, chúng ta sẽ cùng nhau tìm hiểu về Deadlock là gì? làm sao để tránh Deadlock qua các ví dụ trong java.

Deadlock trong java?

DeadLock trong java là một trạng thái trong đó 2 hoặc nhiều thread rơi vào tình trạng chờ đợi lẫn nhau vì mỗi thread giữ một tài nguyên và chờ đợi tài nguyên từ thread khác. Ví dụ ThreadA giữ tài nguyên A và chời đợi tài nguyên B đang bị ThreadB nắm giữ, trong lúc đó ThreadB lại chờ đợi ThreadA trả tài nguyên A để sử dụng dẫn đến ThreadA và ThreadB chờ đợi lẫn nhau mãi mãi.

Cơ chế xử lý deadlock

Ví dụ

 class TestThread {
    public static Object Lock1 = new Object();
    public static Object Lock2 = new Object();

    public static void main(String args[]) {
        ThreadDemo1 T1 = new ThreadDemo1();
        ThreadDemo2 T2 = new ThreadDemo2();
        T1.start();
        T2.start();
    }

    private static class ThreadDemo1 extends Thread {
        public void run() {
            synchronized (Lock1) {
                System.out.println("Thread 1: Holding lock 1...");

                try { Thread.sleep(10); }
                catch (InterruptedException e) {}
                System.out.println("Thread 1: Waiting for lock 2...");

                synchronized (Lock2) {
                    System.out.println("Thread 1: Holding lock 1 & 2...");
                }
            }
        }
    }
    private static class ThreadDemo2 extends Thread {
        public void run() {
            synchronized (Lock2) {
                System.out.println("Thread 2: Holding lock 2...");

                try { Thread.sleep(10); }
                catch (InterruptedException e) {}
                System.out.println("Thread 2: Waiting for lock 1...");

                synchronized (Lock1) {
                    System.out.println("Thread 2: Holding lock 1 & 2...");
                }
            }
        }
    }
}

Chúng ta có 2 thread ThreadDemo1 và ThreadDemo2, hai tài nguyên Lock1 và Lock2.

  • Khi ThreadDemo1 start() nó bắt đầu chiếm giữ Lock1.
  • ThreadDemo1 ngủ 10millis, khoảng thời gian đó ThreadDemo2 start() và chiếm giữ lock2.
  • Sau khi ThreadDemo1 và ThreadDemo2 hoạt động, ThreadDemo1 giữ Lock1 và chờ ThreadDemo2 trả lại Lock2 để sử dụng.
  • ThreadDemo2 giữ Lock2 và đợi ThreadDemo1 trả lại Lock1

Dẫn đến ThreadDemo1 và ThreadDemo2 chờ lẫn nhau gây ra hiện tượng Deadlock.

Để giải quyết Deadlock ở ví dụ trên, mình sẽ sử dụng join() method cho T1 để các các thread bên dưới phải chờ cho đến khi T1 thực thi xong thì bắt đầu khởi chạy.

class TestThread {
    public static Object Lock1 = new Object();
    public static Object Lock2 = new Object();

    public static void main(String args[]) throws InterruptedException {
        ThreadDemo1 T1 = new ThreadDemo1();
        ThreadDemo2 T2 = new ThreadDemo2();
        T1.start();

        // waiting for T1 finished
        T1.join();

        T2.start();
    }

    private static class ThreadDemo1 extends Thread {
        public void run() {
            System.out.println("Thread 1: Holding lock 1...");

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
            }
            System.out.println("Thread 1: Waiting for lock 2...");

            synchronized (Lock2) {
                System.out.println("Thread 1: Holding lock 1 & 2...");
            }
        }
    }

    private static class ThreadDemo2 extends Thread {
        public void run() {
            System.out.println("Thread 2: Holding lock 2...");

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
            }
            System.out.println("Thread 2: Waiting for lock 1...");

            System.out.println("Thread 2: Holding lock 1 & 2...");

        }
    }
}

Output:

Thread 1: Holding lock 1…
Thread 1: Waiting for lock 2…
Thread 1: Holding lock 1 & 2…
Thread 2: Holding lock 2…
Thread 2: Waiting for lock 1…
Thread 2: Holding lock 1 & 2…

Cách tránh Deadlock

Để tránh Deadlock chúng ta phải biết chính xác các khả năng có thể xảy ra Deadlock, việc này khá là khó khăn, mình chém gió vậy thôi chứ gặp Deadlock cũng đắm đuối. Thế nhưng mà chúng ta cứ cố gắng thôi. Dưới đây là một số điểm chúng ta có thể xem qua để giảm bớt khả năng Deadlock xảy ra

  • Cách đơn giản đã được mình nêu trên là sử dụng join() method để các thread khác chờ cho đến khi một thread hoàn thành mới bắt đầu khởi chạy. Lưu ý sử dụng khi nào cần thôi nhé, chứ bất đồng bộ mà cứ chờ nhau thì làm single thread cho rồi!
  • Tránh khoá các tài nguyên không cần thiết để hạn chế Deadlock, không phải cứ cái nào cũng khoá cho 1 thread, chúng ta phải xem xét kỹ xem tài nguyên đó có được sử dụng cho nhiều luồng và giá trị của nó có ảnh hưởng đến kết quả tính toán của các luồng hay không rồi hẳn khoá nhé.
  • Tránh việc khoá lồng nhau, khi một tài nguyên đã được giao cho 1 thread rồi thì đừng cố giao cho các thread khác. Đây là trường hợp phổ biến nhất dẫn đến DeadLock, như ví dụ trên Lock1 và Lock2 mình đã giao cho cả 2 thread chạy song song mà không có các biện pháp cụ thể khác.

Tóm lược

DeadLock là một vấn đề khó trong lập trình đa luồng, ,mà chúng ta cần thực hành nhiều với đa luồng mới biết cách sử lý trong các tình huống cụ thể. Deadlock cũng có thể xảy ra các các truy vấn cơ sở dữ liệu etc, nó có thể xảy ra bất cứ khi nào một tài nguyên được nhiều luồng xử lý tranh giành. 

Deadlock có thể phá huỷ luồng chạy bình thường của chương trình tại thời điểm runtime, ảnh hưởng nghiêm trọnåg đến trải nghiệm người dùng.  

Nguồn tham khảo

https://www.geeksforgeeks.org/introduction-of-deadlock-in-operating-system/

https://www.tutorialspoint.com/java/java_thread_deadlock.htm