Ví dụ về bế tắc đơn giản


92

Tôi muốn giải thích các bế tắc luồng cho người mới. Tôi đã thấy nhiều ví dụ cho deadlock trong quá khứ, một số sử dụng mã và một số sử dụng hình ảnh minh họa (như 4 chiếc xe nổi tiếng ). Cũng có những vấn đề kinh điển dễ gây bế tắc như The Dining Philosophers , nhưng những vấn đề này có thể quá phức tạp để một người mới thực sự có thể nắm bắt hết.

Tôi đang tìm kiếm ví dụ mã đơn giản nhất để minh họa bế tắc là gì. Ví dụ nên:

  1. Liên quan đến một kịch bản lập trình "thực" có ý nghĩa nào đó
  2. Hãy thật ngắn gọn, đơn giản và thẳng thắn

Bạn đề xuất món gì?


Tại sao không sử dụng 4 chiếc xe nổi tiếng, vì nó có vẻ khá đơn giản với tôi.
vehomzzz

2
4 chiếc xe không phải là một kịch bản lập trình, và việc một người mới bắt đầu trừu tượng hóa một vấn đề thành dạng của 4 chiếc xe không phải là điều tầm thường. Tôi sử dụng chúng, nhưng muốn hiển thị một kịch bản lập trình nơi xảy ra bế tắc.
Roee Adler

Câu trả lời:


139

Có thể là một tình huống ngân hàng đơn giản.

class Account {
  double balance;

  void withdraw(double amount){
     balance -= amount;
  } 

  void deposit(double amount){
     balance += amount;
  } 

   void transfer(Account from, Account to, double amount){
        sync(from);
        sync(to);

        from.withdraw(amount);
        to.deposit(amount);

        release(to);
        release(from);
    }

}

Rõ ràng, nếu có hai luồng cố gắng chạy chuyển ( a, b ) và chuyển ( b, a ) cùng một lúc, thì một bế tắc sẽ xảy ra vì chúng cố gắng lấy tài nguyên theo thứ tự ngược lại.

Mã này cũng tuyệt vời để xem xét các giải pháp cho bế tắc. Hi vọng điêu nay co ich!


1
Sẽ thật hoàn hảo nếu bạn hoặc ai đó có thể đưa ra giải pháp cho vấn đề này.
Jacky

2
@Jacky Giải pháp cho vấn đề này được đăng bởi Will Hartung tại đây: stackoverflow.com/questions/13326861/avoid-deadlock-example/…
Piotr Chojnacki

2
Tôi bối rối bởi cú pháp của bạn. Phương thức sync () là gì? Tôi hiểu nếu đồng bộ hóa (từ); ... phát hành (từ); đã được thay thế bằng đồng bộ hóa (từ) {...}
Ellen Spertus

1
@espertus synccó thể được giống như: sync(Account & a) { a.mutex.lock(); }.
vladon

1
javaworld.com/article/2075692/java-concurrency/… (Brian Goetz) giải thích giải pháp cho vấn đề này.
lingareddyk

59

Hãy để tự nhiên giải thích bế tắc,

Bế tắc: Frog vs. Snake

"Tôi rất thích được chứng kiến ​​họ đi theo con đường riêng của mình, nhưng tôi đã kiệt sức", nhiếp ảnh gia nói. "Con ếch đã cố gắng kéo con rắn ra, nhưng con rắn không chịu buông tha" .

nhập mô tả hình ảnh ở đây


59
Dễ thương, nhưng không giải thích cách deadlock xảy ra trong bối cảnh lập trình.
jalf

ok jalf, ít nhất bạn đã chứng minh được sự phản đối. Dù sao thì, nó cũng tương tự như ví dụ "4 ô tô". Một đại diện dễ thương về sự bế tắc trông như thế nào.
Nick Dandoulakis

@Nick Dandoulakis: Trình bày hình ảnh xuất sắc. Hình ảnh giải thích khái niệm bế tắc
Rasmi Ranjan Nayak

@NickDandoulakis - Imho không phải là một ví dụ pic tốt. Mã đơn giản sẽ hữu ích ở đây.
Erran Morad

13
Làm thế nào điều này được cho là DỄ THƯƠNG? Rắn độc và ếch nhái đang ăn thịt lẫn nhau và sự đáng sợ của nó !!!
vikkyhacks

53

Đây là một ví dụ mã từ khoa khoa học máy tính của một trường đại học ở Đài Loan cho thấy một ví dụ java đơn giản với khóa tài nguyên. Điều đó rất "đời thực" với tôi. Mã bên dưới:

/**
 * Adapted from The Java Tutorial
 * Second Edition by Campione, M. and
 * Walrath, K.Addison-Wesley 1998
 */

/**
 * This is a demonstration of how NOT to write multi-threaded programs.
 * It is a program that purposely causes deadlock between two threads that
 * are both trying to acquire locks for the same two resources.
 * To avoid this sort of deadlock when locking multiple resources, all threads
 * should always acquire their locks in the same order.
 **/
public class Deadlock {
  public static void main(String[] args){
    //These are the two resource objects 
    //we'll try to get locks for
    final Object resource1 = "resource1";
    final Object resource2 = "resource2";
    //Here's the first thread.
    //It tries to lock resource1 then resource2
    Thread t1 = new Thread() {
      public void run() {
        //Lock resource 1
        synchronized(resource1){
          System.out.println("Thread 1: locked resource 1");
          //Pause for a bit, simulating some file I/O or 
          //something. Basically, we just want to give the 
          //other thread a chance to run. Threads and deadlock
          //are asynchronous things, but we're trying to force 
          //deadlock to happen here...
          try{ 
            Thread.sleep(50); 
          } catch (InterruptedException e) {}

          //Now wait 'till we can get a lock on resource 2
          synchronized(resource2){
            System.out.println("Thread 1: locked resource 2");
          }
        }
      }
    };

    //Here's the second thread.  
    //It tries to lock resource2 then resource1
    Thread t2 = new Thread(){
      public void run(){
        //This thread locks resource 2 right away
        synchronized(resource2){
          System.out.println("Thread 2: locked resource 2");
          //Then it pauses, for the same reason as the first 
          //thread does
          try{
            Thread.sleep(50); 
          } catch (InterruptedException e){}

          //Then it tries to lock resource1.  
          //But wait!  Thread 1 locked resource1, and 
          //won't release it till it gets a lock on resource2.  
          //This thread holds the lock on resource2, and won't
          //release it till it gets resource1.  
          //We're at an impasse. Neither thread can run, 
          //and the program freezes up.
          synchronized(resource1){
            System.out.println("Thread 2: locked resource 1");
          }
        }
      }
    };

    //Start the two threads. 
    //If all goes as planned, deadlock will occur, 
    //and the program will never exit.
    t1.start(); 
    t2.start();
  }
}

1
Vấn đề là nó không thực sự là một ví dụ "đời thực". Đó là về "tài nguyên 1" và "tài nguyên 2", và nó sẽ được tốt đẹp để thực sự liên hệ này đến một vấn đề lập trình thực tế (Ý tôi là, trực tiếp sử dụng được trong thực tế, với tham chiếu đến các vấn đề tên miền vv)
Jay

7
Ví dụ tốt theo ý kiến ​​của tôi. Cảm ơn.
James Raitsev

Mã này dường như đã được công bố trong một vài cuốn sách khác nhau ... stackoverflow.com/a/11338853/112705
Dan J

15

Nếu cả method1 () và method2 () đều sẽ được gọi bởi hai hoặc nhiều luồng, thì có khả năng xảy ra deadlock vì nếu luồng 1 có được khóa trên đối tượng String trong khi thực hiện phương thức1 () và luồng 2 có được khóa trên đối tượng Integer trong khi thực thi phương thức2 () cả hai sẽ chờ nhau giải phóng khóa trên Số nguyên và Chuỗi để tiến hành thêm, điều này sẽ không bao giờ xảy ra.

public void method1() {
    synchronized (String.class) {
        System.out.println("Acquired lock on String.class object");

        synchronized (Integer.class) {
            System.out.println("Acquired lock on Integer.class object");
        }
    }
}

public void method2() {
    synchronized (Integer.class) {
        System.out.println("Acquired lock on Integer.class object");

        synchronized (String.class) {
            System.out.println("Acquired lock on String.class object");
        }
    }
}

Nhanh chóng và đơn giản. Đẹp.
dùng1068352

13

Một trong những ví dụ về deadlock đơn giản mà tôi đã xem qua.

public class SimpleDeadLock {
   public static Object l1 = new Object();
   public static Object l2 = new Object();
   private int index;
   public static void main(String[] a) {
      Thread t1 = new Thread1();
      Thread t2 = new Thread2();
      t1.start();
      t2.start();
   }
   private static class Thread1 extends Thread {
      public void run() {
         synchronized (l1) {
            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 (l2) {
               System.out.println("Thread 2: Holding lock 1 & 2...");
            }
         }
      }
   }
   private static class Thread2 extends Thread {
      public void run() {
         synchronized (l2) {
            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 (l1) {
               System.out.println("Thread 2: Holding lock 2 & 1...");
            }
         }
      }
   }
}

Tôi thích ví dụ đó. Nhưng tại sao lớp SimpleDeadLock lại miễn Thread? Đó là điều không cần thiết.
Charmin

1
Điều này khá giống với câu trả lời này: stackoverflow.com/a/1385868/1310566 . Và điều đó private int indexđang làm gì ở đó?
Simon Forsberg

6

Đây là một ví dụ đơn giản trong C ++ 11.

#include <mutex>    // mutex
#include <iostream> // cout 
#include <cstdio>   // getchar
#include <thread>   // this_thread, yield
#include <future>   // async
#include <chrono>   // seconds

using namespace std;
mutex _m1;
mutex _m2;

// Deadlock will occur because func12 and func21 acquires the two locks in reverse order

void func12()
{
    unique_lock<mutex> l1(_m1);
    this_thread::yield(); // hint to reschedule
    this_thread::sleep_for( chrono::seconds(1) );
    unique_lock<mutex> l2(_m2 );
}

void func21()
{
    unique_lock<mutex> l2(_m2);
    this_thread::yield(); // hint to reschedule
    this_thread::sleep_for( chrono::seconds(1) );
    unique_lock<mutex> l1(_m1);
}

int main( int argc, char* argv[] )
{
    async(func12);
    func21();
    cout << "All done!"; // this won't be executed because of deadlock
    getchar();
}

5

Hãy xem câu trả lời của tôi cho câu hỏi này . Điểm mấu chốt là bất cứ khi nào hai luồng cần lấy hai tài nguyên khác nhau và làm như vậy theo các thứ tự khác nhau thì bạn có thể gặp bế tắc.


2
Tôi không thực sự thấy điểm trong việc sao chép thông tin từ một câu trả lời khác ở đây. Tôi giả sử rằng nếu bạn nghĩ rằng câu trả lời này có thể được cải thiện, bạn có thể tự chỉnh sửa nó.
djna

Tôi nghĩ rằng tình huống này được gọi là "khóa đảo ngược". Vâng, tôi biết nó được gọi là khóa đảo ngược, vì tôi gọi nó đó, nhưng tôi nghĩ đó cũng là thời hạn của nghệ thuật cho nó :-)
Steve Jessop

4

Một ví dụ mà tôi có thể nghĩ đến là kịch bản Bàn, Đèn pin và Pin. Hãy tưởng tượng một chiếc đèn pin và một cặp pin đặt trên bàn. Nếu bạn bước đến chiếc bàn này và lấy pin trong khi người khác cầm đèn pin, cả hai sẽ buộc phải lúng túng nhìn nhau trong khi chờ xem ai sẽ đặt món đồ của họ trở lại bàn đầu tiên. Đây là một ví dụ về sự bế tắc. Bạn và người ấy đang chờ đợi tài nguyên nhưng không ai trong số bạn từ bỏ tài nguyên của họ.

Tương tự như vậy, trong một chương trình, deadlock xảy ra khi hai hoặc nhiều luồng (bạn và người khác) đang đợi hai hoặc nhiều ổ khóa (đèn pin và pin) được giải phóng và các trường hợp trong chương trình khiến các ổ khóa không bao giờ được giải phóng ( cả hai bạn đều có một mảnh ghép).

Nếu bạn biết java, đây là cách bạn có thể giải quyết vấn đề này:

import java.util.concurrent.locks.*;

public class Deadlock1 {

    public static class Table {

        private static Lock Flashlight = new ReentrantLock();
        private static Lock Batteries = new ReentrantLock();        

        public static void giveFlashLightAndBatteries() {
            try {
                Flashlight.lock();
                Batteries.lock();
                System.out.println("Lights on");
            } finally {
                Batteries.unlock();
                Flashlight.unlock();
            }
        }

        public static void giveBatteriesAndFlashLight() {
            try {
                Batteries.lock();
                Flashlight.lock();
                System.out.println("Lights on");
            } finally {
                Flashlight.unlock();
                Batteries.unlock();
            }
        }
    }

    public static void main(String[] args) {
        // This thread represents person one
        new Thread(new Runnable() {
            public void run() { Table.giveFlashLightAndBatteries(); }
        }).start();

        // This thread represents person two
        new Thread(new Runnable() {
            public void run() { Table.giveBatteriesAndFlashLight(); }
        }).start();
    }
}

Nếu bạn chạy ví dụ này, bạn sẽ nhận thấy rằng đôi khi mọi thứ hoạt động tốt và chính xác. Nhưng đôi khi chương trình của bạn sẽ không in bất cứ thứ gì. Đó là do một người có pin trong khi người khác có đèn pin khiến họ không thể bật đèn pin gây ra bế tắc.

Ví dụ này tương tự như ví dụ được đưa ra bởi các hướng dẫn java: http://docs.oracle.com/javase/tutorial/essential/concurrency/deadlock.html

Một ví dụ khác là ví dụ về vòng lặp:

public class Deadlock2 {

    public static class Loop {
        private static boolean done = false;

        public static synchronized void startLoop() throws InterruptedException {
            while(!done) {
                Thread.sleep(1000);
                System.out.println("Not done");
            }
        }

        public static synchronized void stopLoop() {
            done = true;
        }

    }

    public static void main(String[] args) {
        // This thread starts the loop
        new Thread(new Runnable() {
            public void run() {
                try {
                    Loop.startLoop();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        // This thread stops the loop
        new Thread(new Runnable() {
            public void run() {
                Loop.stopLoop();
            }
        }).start();
    }
}

Ví dụ này có thể in lặp đi lặp lại "Chưa hoàn thành" hoặc không bao giờ có thể in "Chưa hoàn thành". Điều đầu tiên xảy ra bởi vì luồng đầu tiên có được khóa lớp và không bao giờ giải phóng nó ngăn 'stopLoop' bị luồng thứ hai truy cập. Và điều mới nhất xảy ra bởi vì luồng thứ hai bắt đầu trước luồng đầu tiên khiến biến 'done' là true trước khi luồng đầu tiên thực thi.


4
public class DeadLock {
    public static void main(String[] args) throws InterruptedException {
        Thread mainThread = Thread.currentThread();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    mainThread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread1.start();
        thread1.join();
    }
}

3

Mặc dù vậy, tôi coi vấn đề Các triết gia ăn uống là một trong những ví dụ đơn giản hơn trong việc hiển thị các deadlock, vì 4 yêu cầu về deadlock có thể dễ dàng minh họa bằng hình vẽ (đặc biệt là hình tròn chờ).

Tôi coi các ví dụ trong thế giới thực sẽ khó hiểu hơn nhiều đối với người mới, mặc dù tôi không thể nghĩ ra một kịch bản tốt trong thế giới thực ngay bây giờ (tôi tương đối thiếu kinh nghiệm với đồng thời trong thế giới thực).


3

Gần đây tôi đã nhận ra rằng những cuộc chiến giữa các cặp đôi không có gì khác ngoài sự bế tắc .. nơi mà thường thì một trong các quy trình phải sụp đổ để giải quyết nó, tất nhiên đó là quy trình ít ưu tiên hơn (Boy;)).

Đây là sự tương tự ...

Process1: Girl (G) Process2: Boy (B)
Resource1: Xin lỗi Resource2: Chấp nhận sai lầm của chính mình

Điều kiện cần thiết:
1. Loại trừ lẫn nhau: Chỉ một trong hai G hoặc B có thể nói xin lỗi hoặc chấp nhận Sai lầm của chính mình tại một thời điểm.
2. Giữ và Chờ: Tại một thời điểm, một người đang giữ Xin lỗi và người khác Chấp nhận sai lầm của chính mình, một người đang chờ Chấp nhận sai lầm của chính mình để giải thoát xin lỗi, và người khác đang chờ xin lỗi để giải phóng lỗi lầm của chính mình.
3. Không có quyền ưu tiên: Thậm chí Chúa không thể buộc B hoặc G phải đưa ra Xin lỗi hoặc Chấp nhận sai lầm của chính mình. Và tự nguyện? Đùa tôi à ??
4. Vòng tròn Chờ đợi: Một lần nữa, một người giữ lời xin lỗi chờ người khác nhận lỗi về mình, và người giữ lỗi nhận lỗi của mình muốn người khác nói lời xin lỗi trước. Vì vậy, nó là hình tròn.

Vì vậy, bế tắc xảy ra khi tất cả các điều kiện này có hiệu lực cùng một lúc, và điều đó luôn xảy ra trong một cuộc chiến đôi;)

Nguồn: http://www.quora.com/Saurabh-Pandey-3/Posts/Never-ending-couple-fights-a-deadlock


3

Thêm một ví dụ deadlock đơn giản với hai tài nguyên khác nhau và hai luồng đang chờ nhau giải phóng tài nguyên. Trực tiếp từ example.oreilly.com/jenut/Deadlock.java

 public class Deadlock {
  public static void main(String[] args) {
    // These are the two resource objects we'll try to get locks for
    final Object resource1 = "resource1";
    final Object resource2 = "resource2";
    // Here's the first thread.  It tries to lock resource1 then resource2
    Thread t1 = new Thread() {
      public void run() {
        // Lock resource 1
        synchronized(resource1) {
          System.out.println("Thread 1: locked resource 1");

          // Pause for a bit, simulating some file I/O or something.  
          // Basically, we just want to give the other thread a chance to
          // run.  Threads and deadlock are asynchronous things, but we're
          // trying to force deadlock to happen here...
          try { Thread.sleep(50); } catch (InterruptedException e) {}

          // Now wait 'till we can get a lock on resource 2
          synchronized(resource2) {
            System.out.println("Thread 1: locked resource 2");
          }
        }
      }
    };

    // Here's the second thread.  It tries to lock resource2 then resource1
    Thread t2 = new Thread() {
      public void run() {
        // This thread locks resource 2 right away
        synchronized(resource2) {
          System.out.println("Thread 2: locked resource 2");

          // Then it pauses, for the same reason as the first thread does
          try { Thread.sleep(50); } catch (InterruptedException e) {}

          // Then it tries to lock resource1.  But wait!  Thread 1 locked
          // resource1, and won't release it 'till it gets a lock on
          // resource2.  This thread holds the lock on resource2, and won't
          // release it 'till it gets resource1.  We're at an impasse. Neither
          // thread can run, and the program freezes up.
          synchronized(resource1) {
            System.out.println("Thread 2: locked resource 1");
          }
        }
      }
    };

    // Start the two threads. If all goes as planned, deadlock will occur, 
    // and the program will never exit.
    t1.start(); 
    t2.start();
  }
}

If all goes as planned, deadlock will occur, and the program will never exit.Chúng ta có thể làm cho ví dụ này guaranteebế tắc không?
Erran Morad

Đây là mã giống như Kyle đã đăng , tại sao lại thêm một câu trả lời trùng lặp ba năm sau một câu trả lời khác? (và tại sao tôi bình luận về nó, khác ba năm sau đó?)
Simon Forsberg

2

Bế tắc có thể xảy ra trong một tình huống khi một người Girl1đang muốn tán tỉnh Guy2, một người bị người khác bắt gặp Girl2Girl2muốn tán tỉnh một người Guy1bị bắt gặp Girl1. Vì cả hai cô gái đều đang chờ đợi để phá giá nhau, điều kiện được gọi là bế tắc.

class OuchTheGirls
{
    public static void main(String[] args)
    {
        final String resource1 = "Guy1";
        final String resource2 = "Guy2";

        // Girl1 tries to lock resource1 then resource2
        Thread Girl1 = new Thread(() ->
                                  {
                                      synchronized (resource1)
                                      {
                                          System.out.println("Thread 1: locked Guy1");

                                          try { Thread.sleep(100);} catch (Exception e) {}

                                          synchronized (resource2)
                                          {
                                              System.out.println("Thread 1: locked Guy2");
                                          }
                                      }
                                  });

        // Girl2 tries to lock Guy2 then Guy1
        Thread Girl2 = new Thread(() ->
                                  {
                                      synchronized (resource2)
                                      {
                                          System.out.println("Thread 2: locked Guy2");

                                          try { Thread.sleep(100);} catch (Exception e) {}

                                          synchronized (resource1)
                                          {
                                              System.out.println("Thread 2: locked Guy1");
                                          }
                                      }
                                  });


        Girl1.start();
        Girl2.start();
    }
}

1

Các vấn đề sản xuất-người tiêu dùng cùng với vấn đề các triết gia ăn uống có lẽ là đơn giản như nó đang diễn ra để có được. Nó cũng có một số mã giả minh họa nó. Nếu những thứ đó quá phức tạp đối với một người mới, họ nên cố gắng hơn để nắm bắt chúng.


1

Hãy chọn một kịch bản đơn giản có thể xảy ra, trong đó bế tắc có thể xảy ra khi giới thiệu khái niệm cho sinh viên của bạn. Điều này sẽ liên quan đến tối thiểu hai luồng và tối thiểu hai tài nguyên (tôi nghĩ). Mục tiêu là thiết kế một kịch bản trong đó luồng đầu tiên có khóa tài nguyên một và đang chờ khóa trên tài nguyên hai được giải phóng, trong khi đồng thời luồng hai giữ khóa trên tài nguyên hai và đang chờ khóa tài nguyên một sẽ được phát hành.

Nó không thực sự quan trọng các tài nguyên cơ bản là gì; vì lợi ích đơn giản, bạn chỉ có thể tạo chúng thành một cặp tệp mà cả hai luồng đều có thể ghi vào.

CHỈNH SỬA: Điều này giả định không có giao tiếp giữa các quá trình ngoài các khóa được giữ.


1

Tôi thấy rằng hơi khó hiểu khi đọc vấn đề của các triết gia ăn uống, IMHO bế tắc thực sự liên quan đến phân bổ tài nguyên. Xin chia sẻ một ví dụ đơn giản hơn là 2 Y tá cần tranh nhau 3 trang bị để hoàn thành nhiệm vụ. Mặc dù nó được viết bằng java. Một phương thức lock () đơn giản được tạo ra để mô phỏng cách deadlock xảy ra, vì vậy nó cũng có thể áp dụng trong ngôn ngữ lập trình khác. http://www.justexample.com/wp/example-of-deadlock/


1

Ví dụ đơn giản từ https://docs.oracle.com/javase/tutorial/essential/concurrency/deadlock.html

public class Deadlock {

public static void printMessage(String message) {

    System.out.println(String.format("%s %s ", Thread.currentThread().getName(), message));

}

private static class Friend {

    private String name;

    public Friend(String name) {
        this.name = name;
    }

    public void bow(Friend friend) {

        printMessage("Acquiring lock on " + this.name);

        synchronized(this) {
            printMessage("Acquired lock on " + this.name);
            printMessage(name + " bows " + friend.name);
            friend.bowBack(this);
        }

    }

    public void bowBack(Friend friend) {

        printMessage("Acquiring lock on " + this.name);

        synchronized (this) {
            printMessage("Acquired lock on " + this.name);
            printMessage(friend.name + " bows back");
        }

    }

}

public static void main(String[] args) throws InterruptedException {

    Friend one = new Friend("one");
    Friend two = new Friend("two");

    new Thread(new Runnable() {
        @Override
        public void run() {
            one.bow(two);
        }
    }).start();

    new Thread(new Runnable() {
        @Override
        public void run() {
            two.bow(one);
        }
    }).start();
}

}

Đầu ra:

Thread-0 Acquiring lock on one 
Thread-1 Acquiring lock on two 
Thread-0 Acquired lock on one 
Thread-1 Acquired lock on two 
Thread-1 two bows one 
Thread-0 one bows two 
Thread-1 Acquiring lock on one 
Thread-0 Acquiring lock on two 

Thread Dump:

2016-03-14 12:20:09
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.74-b02 mixed mode):

"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x00007f472400a000 nid=0x3783 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Thread-1" #12 prio=5 os_prio=0 tid=0x00007f472420d800 nid=0x37a3 waiting for monitor entry [0x00007f46e89a5000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at com.anantha.algorithms.ThreadJoin$Friend.bowBack(ThreadJoin.java:102)
    - waiting to lock <0x000000076d0583a0> (a com.anantha.algorithms.ThreadJoin$Friend)
    at com.anantha.algorithms.ThreadJoin$Friend.bow(ThreadJoin.java:92)
    - locked <0x000000076d0583e0> (a com.anantha.algorithms.ThreadJoin$Friend)
    at com.anantha.algorithms.ThreadJoin$2.run(ThreadJoin.java:141)
    at java.lang.Thread.run(Thread.java:745)

"Thread-0" #11 prio=5 os_prio=0 tid=0x00007f472420b800 nid=0x37a2 waiting for monitor entry [0x00007f46e8aa6000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at com.anantha.algorithms.ThreadJoin$Friend.bowBack(ThreadJoin.java:102)
    - waiting to lock <0x000000076d0583e0> (a com.anantha.algorithms.ThreadJoin$Friend)
    at com.anantha.algorithms.ThreadJoin$Friend.bow(ThreadJoin.java:92)
    - locked <0x000000076d0583a0> (a com.anantha.algorithms.ThreadJoin$Friend)
    at com.anantha.algorithms.ThreadJoin$1.run(ThreadJoin.java:134)
    at java.lang.Thread.run(Thread.java:745)

"Monitor Ctrl-Break" #10 daemon prio=5 os_prio=0 tid=0x00007f4724211000 nid=0x37a1 runnable [0x00007f46e8def000]
   java.lang.Thread.State: RUNNABLE
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
    at java.net.SocketInputStream.read(SocketInputStream.java:170)
    at java.net.SocketInputStream.read(SocketInputStream.java:141)
    at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
    - locked <0x000000076d20afb8> (a java.io.InputStreamReader)
    at java.io.InputStreamReader.read(InputStreamReader.java:184)
    at java.io.BufferedReader.fill(BufferedReader.java:161)
    at java.io.BufferedReader.readLine(BufferedReader.java:324)
    - locked <0x000000076d20afb8> (a java.io.InputStreamReader)
    at java.io.BufferedReader.readLine(BufferedReader.java:389)
    at com.intellij.rt.execution.application.AppMain$1.run(AppMain.java:93)
    at java.lang.Thread.run(Thread.java:745)

"Service Thread" #9 daemon prio=9 os_prio=0 tid=0x00007f47240c9800 nid=0x3794 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread3" #8 daemon prio=9 os_prio=0 tid=0x00007f47240c6800 nid=0x3793 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread2" #7 daemon prio=9 os_prio=0 tid=0x00007f47240c4000 nid=0x3792 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f47240c2800 nid=0x3791 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f47240bf800 nid=0x3790 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f47240be000 nid=0x378f waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f472408c000 nid=0x378e in Object.wait() [0x00007f46e98c5000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x000000076cf88ee0> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
    - locked <0x000000076cf88ee0> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
    at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f4724087800 nid=0x378d in Object.wait() [0x00007f46e99c6000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x000000076cf86b50> (a java.lang.ref.Reference$Lock)
    at java.lang.Object.wait(Object.java:502)
    at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
    - locked <0x000000076cf86b50> (a java.lang.ref.Reference$Lock)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=0 tid=0x00007f4724080000 nid=0x378c runnable 

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f472401f000 nid=0x3784 runnable 

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f4724021000 nid=0x3785 runnable 

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007f4724022800 nid=0x3786 runnable 

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007f4724024800 nid=0x3787 runnable 

"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x00007f4724026000 nid=0x3788 runnable 

"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x00007f4724028000 nid=0x3789 runnable 

"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x00007f4724029800 nid=0x378a runnable 

"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x00007f472402b800 nid=0x378b runnable 

"VM Periodic Task Thread" os_prio=0 tid=0x00007f47240cc800 nid=0x3795 waiting on condition 

JNI global references: 16


Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x00007f46dc003f08 (object 0x000000076d0583a0, a com.anantha.algorithms.ThreadJoin$Friend),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00007f46dc006008 (object 0x000000076d0583e0, a com.anantha.algorithms.ThreadJoin$Friend),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
    at com.anantha.algorithms.ThreadJoin$Friend.bowBack(ThreadJoin.java:102)
    - waiting to lock <0x000000076d0583a0> (a com.anantha.algorithms.ThreadJoin$Friend)
    at com.anantha.algorithms.ThreadJoin$Friend.bow(ThreadJoin.java:92)
    - locked <0x000000076d0583e0> (a com.anantha.algorithms.ThreadJoin$Friend)
    at com.anantha.algorithms.ThreadJoin$2.run(ThreadJoin.java:141)
    at java.lang.Thread.run(Thread.java:745)
"Thread-0":
    at com.anantha.algorithms.ThreadJoin$Friend.bowBack(ThreadJoin.java:102)
    - waiting to lock <0x000000076d0583e0> (a com.anantha.algorithms.ThreadJoin$Friend)
    at com.anantha.algorithms.ThreadJoin$Friend.bow(ThreadJoin.java:92)
    - locked <0x000000076d0583a0> (a com.anantha.algorithms.ThreadJoin$Friend)
    at com.anantha.algorithms.ThreadJoin$1.run(ThreadJoin.java:134)
    at java.lang.Thread.run(Thread.java:745)

Found 1 deadlock.

Heap
 PSYoungGen      total 74752K, used 9032K [0x000000076cf80000, 0x0000000772280000, 0x00000007c0000000)
  eden space 64512K, 14% used [0x000000076cf80000,0x000000076d8520e8,0x0000000770e80000)
  from space 10240K, 0% used [0x0000000771880000,0x0000000771880000,0x0000000772280000)
  to   space 10240K, 0% used [0x0000000770e80000,0x0000000770e80000,0x0000000771880000)
 ParOldGen       total 171008K, used 0K [0x00000006c6e00000, 0x00000006d1500000, 0x000000076cf80000)
  object space 171008K, 0% used [0x00000006c6e00000,0x00000006c6e00000,0x00000006d1500000)
 Metaspace       used 3183K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 352K, capacity 388K, committed 512K, reserved 1048576K

1

Đây là một trong những bế tắc đơn giản trong Java. Chúng tôi cần hai nguồn lực để chứng minh sự bế tắc. Trong ví dụ dưới đây, một tài nguyên là khóa lớp (thông qua phương thức đồng bộ hóa) và tài nguyên còn lại là số nguyên 'i'

public class DeadLock {

    static int i;
    static int k;

    public static synchronized void m1(){
        System.out.println(Thread.currentThread().getName()+" executing m1. Value of i="+i);

        if(k>0){i++;}

        while(i==0){
            System.out.println(Thread.currentThread().getName()+" waiting in m1 for i to be > 0. Value of i="+i);
            try { Thread.sleep(10000);} catch (InterruptedException e) { e.printStackTrace(); }
        }
    }

    public static void main(String[] args) {

        Thread t1 = new Thread("t1") {
            public void run() {
                m1();
            }
        };

        Thread t2 = new Thread("t2") {
            public void run() {
                try { Thread.sleep(100);} catch (InterruptedException e) { e.printStackTrace(); }
                k++;
                m1();
            }
        };

        t1.start();
        t2.start();
    }
}

1
public class DeadLock {

    public static void main(String[] args) {
        Object resource1 = new Object();
        Object resource2 = new Object();
        SharedObject s = new SharedObject(resource1, resource2);
        TestThread11 t1 = new TestThread11(s);
        TestThread22 t2 = new TestThread22(s);
        t1.start();
        t2.start();
    }

}

class SharedObject {
    Object o1, o2;
    SharedObject(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }
    void m1() {
        synchronized(o1) {
            System.out.println("locked on o1 from m1()");
            synchronized(o2) { 
                System.out.println("locked on o2 from m1()");
            }
        }
    }
    void m2() {
        synchronized(o2) {
            System.out.println("locked on o2 from m2()");
            synchronized(o1) { 
                System.out.println("locked on o1 from m2()");
            }
        }
    }
}

class TestThread11 extends Thread {
    SharedObject s;
    TestThread11(SharedObject s) {
        this.s = s;
    }
    public void run() {
        s.m1();
    }
}

class TestThread22 extends Thread {
    SharedObject s;
    TestThread22(SharedObject s) {
        this.s = s;
    }
    public void run() {
        s.m2();
    }
}

1
Bạn có thể thêm một số văn bản để giải thích câu trả lời của bạn.
Kmeixner

1

Đây là một bế tắc đơn giản trong C #.

void UpdateLabel(string text) {
   lock(this) {
      if(MyLabel.InvokeNeeded) {
        IAsyncResult res =  MyLable.BeginInvoke(delegate() {
             MyLable.Text = text;
            });
         MyLabel.EndInvoke(res);
        } else {
             MyLable.Text = text;
        }
    }
}

Nếu một ngày nào đó, bạn gọi nó từ luồng GUI và một luồng khác cũng gọi nó - bạn có thể gặp bế tắc. Luồng khác đến EndInvoke, đợi luồng GUI thực thi ủy quyền trong khi giữ khóa. Luồng GUI chặn trên cùng một khóa chờ luồng khác giải phóng nó - điều này sẽ không xảy ra vì luồng GUI sẽ không bao giờ có sẵn để thực thi ủy quyền mà luồng khác đang đợi. (tất nhiên, khóa ở đây không hoàn toàn cần thiết - có lẽ cũng không phải là EndInvoke, nhưng trong một tình huống phức tạp hơn một chút, người gọi có thể nhận được khóa vì những lý do khác, dẫn đến cùng một bế tắc.)


0
package test.concurrent;
public class DeadLockTest {
   private static long sleepMillis;
   private final Object lock1 = new Object();
   private final Object lock2 = new Object();

   public static void main(String[] args) {
       sleepMillis = Long.parseLong(args[0]);
       DeadLockTest test = new DeadLockTest();
       test.doTest();
   }

   private void doTest() {
       Thread t1 = new Thread(new Runnable() {
           public void run() {
               lock12();
           }
       });
       Thread t2 = new Thread(new Runnable() {
           public void run() {
               lock21();
           }
       });
       t1.start();
       t2.start();
   }

   private void lock12() {
       synchronized (lock1) {
           sleep();
           synchronized (lock2) {
               sleep();
           }
       }
   }

   private void lock21() {
       synchronized (lock2) {
           sleep();
           synchronized (lock1) {
               sleep();
           }
       }
   }

   private void sleep() {
       try {
           Thread.sleep(sleepMillis);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }
}
To run the deadlock test with sleep time 1 millisecond:
java -cp . test.concurrent.DeadLockTest 1

0
public class DeadlockProg {

    /**
     * @Gowtham Chitimi Reddy IIT(BHU);
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        final Object ob1 = new Object();
        final Object ob2 = new Object();
        Thread t1 = new Thread(){
            public void run(){
                synchronized(ob1){
                    try{
                        Thread.sleep(100);
                    }
                    catch(InterruptedException e){
                        System.out.println("Error catched");
                    }
                    synchronized(ob2){

                    }
                }

            }
        };
        Thread t2 = new Thread(){
            public void run(){
                synchronized(ob2){
                    try{
                        Thread.sleep(100);
                    }
                    catch(InterruptedException e){
                        System.out.println("Error catched");
                    }
                    synchronized(ob1){                      
                    }
                }               
            }
        };
        t1.start();
        t2.start();
    }

}

0
package ForkBlur;

public class DeadLockTest {
  public static void main(String args[]) {

    final DeadLockTest t1 = new DeadLockTest();
    final DeadLockTest t2 = new DeadLockTest();

    Runnable r1 = new Runnable() {

        @Override
        public void run() {
            try {

                synchronized (t1) {
                    System.out
                            .println("r1 has locked t1, now going to sleep");
                    Thread.sleep(100);
                    System.out
                            .println("r1 has awake , now going to aquire lock for t2");
                    synchronized (t2) {
                        Thread.sleep(100);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    };

    Runnable r2 = new Runnable() {

        @Override
        public void run() {
            try {

                synchronized (t2) {
                    System.out
                            .println("r2 has aquire the lock of t2 now going to sleep");
                    Thread.sleep(100);
                    System.out
                            .println("r2 is awake , now going to aquire the lock from t1");
                    synchronized (t1) {
                        Thread.sleep(100);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    };

    new Thread(r1).start();
    new Thread(r2).start();
  }
}

0

Tôi đã tạo một Ví dụ DeadLock làm việc cực kỳ đơn giản: -

package com.thread.deadlock;

public class ThreadDeadLockClient {

    public static void main(String[] args) {
        ThreadDeadLockObject1 threadDeadLockA = new ThreadDeadLockObject1("threadDeadLockA");
        ThreadDeadLockObject2 threadDeadLockB = new ThreadDeadLockObject2("threadDeadLockB");

        new Thread(new Runnable() {

            @Override
            public void run() {
                threadDeadLockA.methodA(threadDeadLockB);

            }
        }).start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                threadDeadLockB.methodB(threadDeadLockA);

            }
        }).start();
    }
}

package com.thread.deadlock;

public class ThreadDeadLockObject1 {

    private String name;

    ThreadDeadLockObject1(String name){
        this.name = name;
    }

    public  synchronized void methodA(ThreadDeadLockObject2 threadDeadLockObject2) {
        System.out.println("In MethodA "+" Current Object--> "+this.getName()+" Object passed as parameter--> "+threadDeadLockObject2.getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        threadDeadLockObject2.methodB(this);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }   
}

package com.thread.deadlock;

public class ThreadDeadLockObject2 {

    private String name;

    ThreadDeadLockObject2(String name){
        this.name = name;
    }

    public  synchronized void methodB(ThreadDeadLockObject1 threadDeadLockObject1) {
        System.out.println("In MethodB "+" Current Object--> "+this.getName()+" Object passed as parameter--> "+threadDeadLockObject1.getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        threadDeadLockObject1.methodA(this);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Trong ví dụ trên, 2 luồng đang thực thi các phương thức đồng bộ của hai đối tượng khác nhau. Phương thức đồng bộ hóaA được gọi bởi đối tượng threadDeadLockA và phương thức đồng bộ hóaB được gọi bởi đối tượng threadDeadLockB. Trong methodA, một tham chiếu của threadDeadLockB được chuyển và trong methodB, một tham chiếu của threadDeadLockA được chuyển. Bây giờ mỗi luồng cố gắng khóa đối tượng khác. Trong methodA, luồng đang giữ khóa trên threadDeadLockA đang cố gắng lấy khóa trên đối tượng threadDeadLockB và tương tự trong methodB, luồng đang giữ khóa trên threadDeadLockB đang cố lấy khóa trên threadDeadLockA. Vì vậy, cả hai luồng sẽ đợi mãi mãi tạo ra một bế tắc.


0

Hãy để tôi giải thích rõ hơn bằng cách sử dụng một ví dụ có nhiều hơn 2 chủ đề.

Giả sử bạn có n luồng mỗi khóa lần lượt giữ L1, L2, ..., Ln. Bây giờ giả sử, bắt đầu từ luồng 1, mỗi luồng cố gắng lấy được khóa của luồng hàng xóm của nó. Vì vậy, luồng 1 bị chặn vì cố gắng lấy L2 (vì L2 thuộc sở hữu của luồng 2), luồng 2 bị chặn đối với L3, v.v. Luồng n bị chặn đối với L1. Đây bây giờ là một bế tắc vì không có luồng nào có thể thực thi.

class ImportantWork{
   synchronized void callAnother(){     
   }
   synchronized void call(ImportantWork work) throws InterruptedException{
     Thread.sleep(100);
     work.callAnother();
   }
}
class Task implements Runnable{
  ImportantWork myWork, otherWork;
  public void run(){
    try {
      myWork.call(otherWork);
    } catch (InterruptedException e) {      
    }
  }
}
class DeadlockTest{
  public static void main(String args[]){
    ImportantWork work1=new ImportantWork();
    ImportantWork work2=new ImportantWork();
    ImportantWork work3=new ImportantWork();
    Task task1=new Task(); 
    task1.myWork=work1;
    task1.otherWork=work2;

    Task task2=new Task(); 
    task2.myWork=work2;
    task2.otherWork=work3;

    Task task3=new Task(); 
    task3.myWork=work3;
    task3.otherWork=work1;

    new Thread(task1).start();
    new Thread(task2).start();
    new Thread(task3).start();
  }
}

Trong ví dụ trên, bạn có thể thấy rằng có ba luồng chứa Runnables task1, task2 và task3. Trước câu lệnh, sleep(100)các luồng nhận được các khóa của ba đối tượng công việc khi chúng nhập call()phương thức (do sự hiện diện của synchronized). Nhưng ngay sau khi họ cố gắng callAnother()vào đối tượng của chủ đề hàng xóm của mình, chúng đã bị chặn, dẫn đến bế tắc, vì khóa của những đối tượng đó đã bị lấy mất.


0
CountDownLatch countDownLatch = new CountDownLatch(1);
ExecutorService executorService = ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
    Future<?> future = executorService.submit(() -> {
        System.out.println("generated task");
    });
    countDownLatch.countDown();
    try {
        future.get();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
         e.printStackTrace();
    }
});


countDownLatch.await();
executorService.shutdown();

0

Một cách lén lút để deadlock chỉ với một luồng duy nhất là cố gắng khóa cùng một mutex (không đệ quy) hai lần. Đây có thể không phải là ví dụ đơn giản mà bạn đang tìm kiếm, nhưng chắc chắn rằng tôi đã gặp những trường hợp như vậy rồi.

#include <mutex>
#include <iostream>

int main()
{
  std::mutex m;
  m.lock();
  m.lock();
  std::cout << "Expect never to get here because of a deadlock!";
}

0

Đây là ví dụ chi tiết của tôi về bế tắc , sau khi dành nhiều thời gian. Hy vọng nó giúp :)

package deadlock;

public class DeadlockApp {

    String s1 = "hello";
    String s2 = "world";

    Thread th1 = new Thread() {
        public void run() {
            System.out.println("Thread th1 has started");
            synchronized (s1) { //A lock is created internally (holds access of s1), lock will be released or unlocked for s1, only when it exits the block Line #23
                System.out.println("Executing first synchronized block of th1!");
                try {
                    Thread.sleep(1000);
                } catch(InterruptedException ex) {
                    System.out.println("Exception is caught in th1");
                }
                System.out.println("Waiting for the lock to be released from parrallel thread th1");
                synchronized (s2) { //As another has runned parallely Line #32, lock has been created for s2
                    System.out.println(s1 + s2);
                }

            }
            System.out.println("Thread th1 has executed");
        }
    };


    Thread th2 = new Thread() {
        public void run() {
            System.out.println("Thread th2 has started");
            synchronized (s2) { //A lock is created internally (holds access of s2), lock will be released or unlocked for s2, only when it exits the block Line #44
                System.out.println("Executing first synchronized block of th2!");
                try {
                    Thread.sleep(1000);
                } catch(InterruptedException ex) {
                    System.out.println("Exception is caught in th2");
                }
                System.out.println("Waiting for the lock to be released from parrallel thread th2");
                synchronized (s1) { //As another has runned parallely Line #11, lock has been created for s1
                    System.out.println(s1 + s2);
                }

            }
            System.out.println("Thread th2 has executed");
        }
    };

    public static void main(String[] args) {
        DeadlockApp deadLock = new DeadlockApp();
        deadLock.th1.start();
        deadLock.th2.start();
        //Line #51 and #52 runs parallely on executing the program, a lock is created inside synchronized method
        //A lock is nothing but, something like a blocker or wall, which holds access of the variable from being used by others.
        //Locked object is accessible, only when it is unlocked (i.e exiting  the synchronized block)
        //Lock cannot be created for primitive types (ex: int, float, double)
        //Dont forget to add thread.sleep(time) because if not added, then object access will not be at same time for both threads to create Deadlock (not actual runtime with lots of threads) 
        //This is a simple program, so we added sleep90 to create Deadlock, it will execute successfully, if it is removed. 
    }

    //Happy coding -- Parthasarathy S
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.