Masalah Ahli Falsafah Makan di Jawa

1. Pengenalan

Masalah The Philosophers Makan adalah salah satu masalah klasik yang digunakan untuk menerangkan masalah penyegerakan dalam persekitaran berbilang benang dan menggambarkan teknik untuk menyelesaikannya . Dijkstra pertama kali merumuskan masalah ini dan mengemukakannya mengenai komputer yang mengakses periferal pemacu pita.

Rumusan ini diberikan oleh Tony Hoare, yang juga terkenal kerana mencipta algoritma penyortiran quicksort. Dalam artikel ini, kami menganalisis masalah yang terkenal ini dan menetapkan penyelesaian yang popular.

2. Masalahnya

Gambar rajah di atas menunjukkan masalah. Terdapat lima ahli falsafah senyap (P1 - P5) duduk di sekitar meja bulat, menghabiskan hidup mereka makan dan berfikir.

Terdapat lima garpu untuk mereka kongsi (1 - 5) dan untuk dapat makan, seorang ahli falsafah perlu memiliki garpu di kedua tangannya. Setelah makan, dia meletakkan kedua-duanya dan kemudian mereka dapat dipilih oleh ahli falsafah lain yang mengulangi kitaran yang sama.

Tujuannya adalah untuk menghasilkan skema / protokol yang membantu para ahli falsafah mencapai matlamat mereka untuk makan dan berfikir tanpa mati kelaparan.

3. Penyelesaian

Penyelesaian awal adalah membuat setiap ahli falsafah mengikuti protokol berikut:

while(true) { // Initially, thinking about life, universe, and everything think(); // Take a break from thinking, hungry now pick_up_left_fork(); pick_up_right_fork(); eat(); put_down_right_fork(); put_down_left_fork(); // Not hungry anymore. Back to thinking! } 

Seperti yang dijelaskan oleh kod semu di atas, setiap ahli falsafah pada mulanya berfikir. Setelah jangka masa tertentu, ahli falsafah berasa lapar dan ingin makan.

Pada ketika ini, dia meraih garpu di kedua-dua sisinya dan setelah dia mendapatkan kedua-duanya, terus makan . Setelah makan selesai, ahli falsafah kemudian meletakkan garpu, sehingga ia tersedia untuk jirannya.

4. Pelaksanaan

Kami memodelkan setiap ahli falsafah kami sebagai kelas yang melaksanakan antara muka Runnable sehingga kami dapat menjalankannya sebagai utas yang terpisah. Setiap Ahli Falsafah mempunyai akses ke dua garpu di sebelah kiri dan kanannya:

public class Philosopher implements Runnable { // The forks on either side of this Philosopher private Object leftFork; private Object rightFork; public Philosopher(Object leftFork, Object rightFork) { this.leftFork = leftFork; this.rightFork = rightFork; } @Override public void run() { // Yet to populate this method } }

Kami juga mempunyai kaedah yang memerintahkan seorang Ahli Filsuf untuk melakukan tindakan - makan, berfikir, atau dapatkan garpu sebagai persediaan untuk makan:

public class Philosopher implements Runnable { // Member variables, standard constructor private void doAction(String action) throws InterruptedException { System.out.println( Thread.currentThread().getName() + " " + action); Thread.sleep(((int) (Math.random() * 100))); } // Rest of the methods written earlier }

Seperti yang ditunjukkan dalam kod di atas, setiap tindakan disimulasikan dengan menangguhkan utas pemanggil untuk waktu yang rawak, sehingga perintah pelaksanaan tidak diberlakukan hanya dengan waktu saja.

Sekarang, mari kita laksanakan logik teras seorang Ahli Falsafah .

Untuk mensimulasikan memperoleh garpu, kita perlu menguncinya sehingga tidak ada dua utas Philosopher yang memperolehnya pada masa yang sama.

Untuk mencapainya, kami menggunakan kata kunci yang disegerakkan untuk memperoleh monitor dalaman objek garpu dan mengelakkan utas lain melakukan perkara yang sama. Panduan untuk kata kunci yang disegerakkan di Java boleh didapati di sini. Kami terus melaksanakan kaedah run () di kelas Philosopher sekarang:

public class Philosopher implements Runnable { // Member variables, methods defined earlier @Override public void run() { try { while (true) { // thinking doAction(System.nanoTime() + ": Thinking"); synchronized (leftFork) { doAction( System.nanoTime() + ": Picked up left fork"); synchronized (rightFork) { // eating doAction( System.nanoTime() + ": Picked up right fork - eating"); doAction( System.nanoTime() + ": Put down right fork"); } // Back to thinking doAction( System.nanoTime() + ": Put down left fork. Back to thinking"); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } } } 

Skema ini betul-betul menerapkan yang dijelaskan sebelumnya: seorang Ahli Filsuf berfikir sebentar dan kemudian memutuskan untuk makan.

Selepas ini, dia memperoleh garpu di sebelah kiri dan kanannya dan mula makan. Setelah selesai, dia meletakkan garpu ke bawah. Kami juga menambahkan cap waktu untuk setiap tindakan, yang akan membantu kami memahami urutan kejadian.

Untuk memulakan keseluruhan proses, kami menulis pelanggan yang mencipta 5 Falsafah sebagai utas dan memulakan semuanya:

public class DiningPhilosophers { public static void main(String[] args) throws Exception { Philosopher[] philosophers = new Philosopher[5]; Object[] forks = new Object[philosophers.length]; for (int i = 0; i < forks.length; i++) { forks[i] = new Object(); } for (int i = 0; i < philosophers.length; i++) { Object leftFork = forks[i]; Object rightFork = forks[(i + 1) % forks.length]; philosophers[i] = new Philosopher(leftFork, rightFork); Thread t = new Thread(philosophers[i], "Philosopher " + (i + 1)); t.start(); } } }

Kami memodelkan setiap garpu sebagai objek Java generik dan membuat sebilangan besar dari mereka kerana ada ahli falsafah. Kami melewati setiap filsuf garpu kiri dan kanannya yang cuba dikunci dengan menggunakan kata kunci yang disegerakkan .

Menjalankan kod ini menghasilkan output yang serupa dengan yang berikut. Keluaran anda kemungkinan besar akan berbeza dari yang diberikan di bawah, kebanyakannya kerana kaedah sleep () dipanggil untuk selang waktu yang berbeza:

Philosopher 1 8038014601251: Thinking Philosopher 2 8038014828862: Thinking Philosopher 3 8038015066722: Thinking Philosopher 4 8038015284511: Thinking Philosopher 5 8038015468564: Thinking Philosopher 1 8038016857288: Picked up left fork Philosopher 1 8038022332758: Picked up right fork - eating Philosopher 3 8038028886069: Picked up left fork Philosopher 4 8038063952219: Picked up left fork Philosopher 1 8038067505168: Put down right fork Philosopher 2 8038089505264: Picked up left fork Philosopher 1 8038089505264: Put down left fork. Back to thinking Philosopher 5 8038111040317: Picked up left fork

Semua Philosopher pada mulanya mula berfikir, dan kita melihat bahawa Philosopher 1 terus mengambil garpu kiri dan kanan, kemudian makan dan meneruskan meletakkan kedua-duanya ke bawah, setelah itu `Philosopher 5` mengambilnya.

5. Masalah Dengan Penyelesaiannya: Kebuntuan

Walaupun nampaknya penyelesaian di atas betul, ada masalah kebuntuan yang timbul.

Kebuntuan adalah keadaan di mana kemajuan sistem dihentikan kerana setiap proses menunggu untuk memperoleh sumber yang dipegang oleh beberapa proses lain.

Kami dapat mengesahkan perkara yang sama dengan menjalankan kod di atas beberapa kali dan memeriksa bahawa beberapa kali, kodnya hanya tergantung. Berikut adalah contoh output yang menunjukkan masalah di atas:

Philosopher 1 8487540546530: Thinking Philosopher 2 8487542012975: Thinking Philosopher 3 8487543057508: Thinking Philosopher 4 8487543318428: Thinking Philosopher 5 8487544590144: Thinking Philosopher 3 8487589069046: Picked up left fork Philosopher 1 8487596641267: Picked up left fork Philosopher 5 8487597646086: Picked up left fork Philosopher 4 8487617680958: Picked up left fork Philosopher 2 8487631148853: Picked up left fork

Dalam situasi ini, setiap ahli Filsuf telah memperoleh garpu kirinya, tetapi tidak dapat memperoleh garpu kanannya, kerana jirannya telah memperolehnya. Keadaan ini biasanya dikenali sebagai menunggu bulat dan merupakan salah satu keadaan yang mengakibatkan kebuntuan dan menghalang kemajuan sistem.

6. Menyelesaikan Kebuntuan

As we saw above, the primary reason for a deadlock is the circular wait condition where each process waits upon a resource that's being held by some other process. Hence, to avoid a deadlock situation we need to make sure that the circular wait condition is broken. There are several ways to achieve this, the simplest one being the follows:

All Philosophers reach for their left fork first, except one who first reaches for his right fork.

We implement this in our existing code by making a relatively minor change in code:

public class DiningPhilosophers { public static void main(String[] args) throws Exception { final Philosopher[] philosophers = new Philosopher[5]; Object[] forks = new Object[philosophers.length]; for (int i = 0; i < forks.length; i++) { forks[i] = new Object(); } for (int i = 0; i < philosophers.length; i++) { Object leftFork = forks[i]; Object rightFork = forks[(i + 1) % forks.length]; if (i == philosophers.length - 1) { // The last philosopher picks up the right fork first philosophers[i] = new Philosopher(rightFork, leftFork); } else { philosophers[i] = new Philosopher(leftFork, rightFork); } Thread t = new Thread(philosophers[i], "Philosopher " + (i + 1)); t.start(); } } } 

The change comes in lines 17-19 of the above code, where we introduce the condition that makes the last philosopher reach for his right fork first, instead of the left. This breaks the circular wait condition and we can avert the deadlock.

Hasil berikut menunjukkan salah satu kes di mana semua Ahli Filsafat berpeluang berfikir dan makan, tanpa menyebabkan kebuntuan:

Philosopher 1 88519839556188: Thinking Philosopher 2 88519840186495: Thinking Philosopher 3 88519840647695: Thinking Philosopher 4 88519840870182: Thinking Philosopher 5 88519840956443: Thinking Philosopher 3 88519864404195: Picked up left fork Philosopher 5 88519871990082: Picked up left fork Philosopher 4 88519874059504: Picked up left fork Philosopher 5 88519876989405: Picked up right fork - eating Philosopher 2 88519935045524: Picked up left fork Philosopher 5 88519951109805: Put down right fork Philosopher 4 88519997119634: Picked up right fork - eating Philosopher 5 88519997113229: Put down left fork. Back to thinking Philosopher 5 88520011135846: Thinking Philosopher 1 88520011129013: Picked up left fork Philosopher 4 88520028194269: Put down right fork Philosopher 4 88520057160194: Put down left fork. Back to thinking Philosopher 3 88520067162257: Picked up right fork - eating Philosopher 4 88520067158414: Thinking Philosopher 3 88520160247801: Put down right fork Philosopher 4 88520249049308: Picked up left fork Philosopher 3 88520249119769: Put down left fork. Back to thinking 

Ia dapat disahkan dengan menjalankan kod beberapa kali, bahawa sistem ini bebas dari situasi kebuntuan yang berlaku sebelumnya.

7. Kesimpulannya

Dalam artikel ini, kami meneroka masalah Ahli Falsafah Makan yang terkenal dan konsep menunggu dan kebuntuan pekeliling . Kami mengkodkan penyelesaian mudah yang menyebabkan kebuntuan dan membuat perubahan sederhana untuk menghentikan menunggu bulat dan mengelakkan kebuntuan. Ini baru permulaan, dan penyelesaian yang lebih canggih memang ada.

Kod untuk artikel ini boleh didapati di GitHub.