Cari artikel di situs ini

Kunci Mati (Deadlock)

Versi ramah cetakVersi ramah cetak

Thread bisa diblok dan objek bisa memanggil metode synchronized ke suatu objek sehingga objek lain tidak bisa mengakses objek tersebut hingga kuncinya dilepas. Karenanya mungkin saja satu thread tersangkut menunggu suatu thread, akan tetapi thread yang ditunggu ini juga sedang menunggu thread lain, dan seterusnya. Jika rangkaian kunci kembali ke thread pertama, maka semua thread akan diam menunggu satu sama lain dan tidak akan pernah jalan. Kasus ini dinamakan kunci mati (deadlock).

Jika program yang kita buat tiba-tiba mengalamai deadlock, kita akan segera tahu dan memperbaikinya. Akan tetapi permasalahan utamanya adalah deadlock sulit untuk dideteksi. Sering kali program yang kita buat tampak baik-baik saja, akan tetapi mungkin menyimpan bahaya laten deadlock, yang suatu saat nanti terjadi ketika program sudah dirilis (bahkan sering kali deadlock ini juga tidak bisa direproduksi sehingga menyulitkan debugging). Mencegah deadlock dengan membuat desain program yang lebih hati-hati sangat penting ketika kita membuat program banyak thread.

Mari kita lihat contoh klasik dari deadlock yang ditemukan oleh Dijkstra, yaitu "ilmuwan yang sedang makan". Misalnya ada 5 orang ilmuwan (kita bisa mengganti berapa saja). Ilmuwan-ilmuwan ini menghabiskan sebagian waktu untuk berfikir dan sebagian lagi untuk makan. Ketika mereka berfikir, mereka tidak membutuhkan apa-apa, akan tetapi ketika mereka makan, mereka duduk di meja dengan jumlah alat makan yang terbatas. Mereka membutuhkan dua garpu untuk mengambil spaghetti dari mangkok di tengah meja.

Kesulitannya adalah karena ilmuwan tidak punya uang, mereka tidak mampu untuk membeli banyak garpu. Hanya ada 5 garpu yang tersedia. Garpu-garpu ini diletakkan di meja tersebar di dekat masing-masing ilmuwan ini. Ketika ilmuwan ingin makan, dia harus mengambil garpu di sebelah kiri dan kanannya. Jika ilmuwan di sebelahnya sedang menggunakan garpu tersebut, maka ia harus menunggu hingga garpunya selesai digunakan.

Persoalan ini menjadi menarik karena menjelaskan bahwa program yang sepertinya berjalan dengan benar akan tetapi mudah terkena deadlock. Kita bisa mengganti beberapa konstanta sehingga deadlock bisa lebih cepat terjadi, atau bisa dicegah sama sekali. Parameter-parameter yang bisa diganti adalah konstanta bertipe final static int di awal deklarasi kelas IlmuwanMakan. Jika kita menggunakan banyak ilmuwan dan waktu berfikir yang lama, deadlock akan lebih jarang terjadi.

package com.lyracc.ilmuwanmakan;
 
import java.util.*;
 
class Garpu {
    private static int hitung = 0;
    private int nomor = hitung++;
 
    public String toString() {
        return "garpu " + nomor;
    }
} // akhir kelas Garpu
 
class Ilmuwan extends Thread {
    private static Random acak = new Random();
    private static int hitung = 0;
    private int nomor = hitung++;
    private Garpu garpuKiri;
    private Garpu garpuKanan;
    static int waktuFikirMaks = IlmuwanMakan.WAKTU_FIKIR_MAKS;
 
    public Ilmuwan(Garpu kiri, Garpu kanan) {
        garpuKiri = kiri;
        garpuKanan = kanan;
        start();
    }
 
    // Ilmuwan berfikir, gunakan sleep untuk mensimulasi
    public void berfikir() {
        System.out.println(this + " berfikir");
        try {
            sleep(acak.nextInt(waktuFikirMaks));
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
 
    // Ilmuwan makan
    public void makan() {
        // cek apakah garpu kirinya tersedia
        synchronized (garpuKiri) {
            System.out.println(this + " punya " + this.garpuKiri
                    + ". Menunggu  " + this.garpuKanan);
 
            // kemudian cek apakah garpu kanannya tersedia
            synchronized (garpuKanan) {
                System.out.println(this + " makan");
            }
        }
    }
 
    public String toString() {
        return "Ilmuwan " + nomor;
    }
 
    // Metode ketika thread dijalankan
    // masing-masing ilmuwan akan berfikir kemudian makan
    // begitu seterusnya
    public void run() {
        while (true) {
            berfikir();
            makan();
        }
    }
} // akhir kelas ilmuwan
 
// Kelas timeout untuk menghentikan proses setelah
// waktu yang ditentukan
class Timeout extends Timer {
    public Timeout(int jeda, final String pesan) {
        super(true); // Daemon thread
        schedule(new TimerTask() {
            public void run() {
                System.out.println(pesan);
                System.exit(0);
            }
        }, jeda);
    }
} // akhir kelas Timeout
 
// Kelas utama
public class IlmuwanMakan {
    final static int JUMLAH_ILMUWAN = 3;   // bisa diganti
    final static int WAKTU_FIKIR_MAKS = 10; // mili detik, bisa diganti
    final static boolean DEADLOCK = true;  // ubah ini menjadi false untuk mencegah deadlock
    final static int WAKTU_TIMEOUT = 10000; // mili detik atau buat 0 jika tidak ingin timeout
 
    public static void main(String[] args) {
        // Buat array ilmuwan sejumlah JUMLAH_ILMUWAN
        Ilmuwan[] ilmuwan = new Ilmuwan[JUMLAH_ILMUWAN];
 
        // Mula-mula buat 2 garpu
        Garpu kiri = new Garpu();
        Garpu kanan = new Garpu();
 
        // Garpu pertama hanya sebagai penanda
        // yaitu garpu di kiri ilmuwan pertama
        Garpu pertama = kiri;
 
        int i = 0;
 
        // buat masing-masing ilmuwan
        // yang pertama memiliki garpu kiri dan kanan
        // ilmuwan kedua duduk di sebelah kanan ilmuwan pertama
        // sehingga garpu kirinya adalah garpu kanan ilmuwan pertama
        // buat garpu baru untuk garpu kanannya
        // demikian seterusnya hingga JUMLAH_ILMUWAN minus 1
        while (i < ilmuwan.length - 1) {
            ilmuwan[i++] = new Ilmuwan(kiri, kanan);
            kiri = kanan;
            kanan = new Garpu();
        }
 
        // Sekarang buat ilmuwan terakhir
        // Jika kita ingin membuat deadlock (makan menghadap meja) :
        // - garpu kirinya adalah garpu kanan ilmuwan sebelumnya
        // - garpu kanannya adalah garpu kiri ilmuwan pertama 
        //
        // Jika tidak (makan berbalik arah)
        // - garpu kirinya adalah garpu kiri ilmuwan pertama
        // - garpu kanannya adalah garpu kanan ilmuwan sebelumnya
        if (DEADLOCK)
            ilmuwan[i] = new Ilmuwan(kiri, pertama);
        else
            ilmuwan[i] = new Ilmuwan(pertama, kiri);
 
        // Keluar dari program setelah jeda waktu selesai
        if (WAKTU_TIMEOUT > 0)
            new Timeout(WAKTU_TIMEOUT, "Waktu habis..");
    }
}

Kelas Garpu dan Ilmuwan menggunakan penghitung otomatis untuk memberi nomor identifikasi tersendiri untuk setiap objek Garpu dan Ilmuwan yang diciptakan. Setiap Ilmuwan diberi referensi ke garpu kiri dan garpu kanan. Garpu-garpu ini akan diambil oleh ilmuwan ketika hendak makan.

Variabel statik waktuFikirMaks adalah waktu maksimum yang digunakan oleh ilmuwan untuk berfikir. Jika nilainya tidak nol, maka nilai variabel ini akan digunakan sebagai argumen perintah sleep() dalam kelas Ilmuwan. Mungkin kita beranggapan dengan mengubah waktu berfikir setiap ilmuwan, mereka tidak akan makan secara bersamaan sehingga kemungkinan terjadinya deadlock menjadi lebih kecil. Padahal sebenarnya tidak demikian.

Di dalam metode makan(), seorang ilmuwan akan mengambil garpu dengan melakukan sinkronisasi pada garpu tersebut. Jika garpu sedang digunakan oleh ilmuwan lain, maka ilmuwan tersebut akan menunggu hingga garpu selesai digunakan. Mula-mula garpu kiri dahulu yang dicoba untuk diambil, baru kemudian garpu kanan. Setelah digunakan, garpu kanan akan dilepas terlebih dahulu baru kemudian garpu kiri.

Dalam metode run() serorang ilmuwan makan dan berfikir terus menerus.

Ada empat konstanta yang bisa kita ubah-ubah di dalam kelas IlmuwanMakan. JUMLAH_ILMUWAN dan WAKTU_FIKIR_MAKS adalah banyaknya ilmuwan yang ada dan waktu fikir ilmuwan seperti dijelaskan sebelumnya. Variabel ketiga DEADLOCK berupa boolean yang bisa berisi true atau false. Jika bernilai true maka program cepat atua lambat pasti akan mengalami deadlock. Untuk menghindari deadlock, kita bisa menggantinya dengan false. Variabel keempat, yaitu WAKTU_TIMEOUT digunakan untuk menghentikan semua proses pada waktu tertentu. Pada proses yang sulit atau tidak mungkin deadlock (jika variabel DEADLOCK false, atau jumlah ilmuwan banyak, atau waktu fikir ilmuwan sangat panjang), maka proses akan berhenti pada waktu time out, seringkali sebelum deadlock terjadi.

Setelah array objek Ilmuwan dibuat, dua objek Garpu akan dibuat. Objek pertama, juga disimpan dalam variabel pertama akan digunakan kemudian. Setiap objek ilmuwan akan diberi garpu kiri dan kanannya, kecuali objek ilmuwan terakhir. Setiap kali, garpu kiri dipindah ke garpu kanan. Bayangkan meja ilmuwan dibuat dalam urutan melingkar berlawanan arah jarum jam. Garpu kiri ilmuwan baru adalah garpu kanan ilmuwan sebelumnya. Sedangkan garpu kanan ilmuwan baru adalah objek garpu baru.

Pada versi di mana DEADLOCK bernilai true, garpu kiri ilmuwan terakhir adalah garpu kanan ilmuwan sebelumnya, akan tetapi garpu kanannya adalah garpu pertama, karena semua ilmuwan duduk pada posisi melingkar. Dengan pengaturan seperti ini, mungkin saja pada suatu waktu semua ilmuwan akan makan dan saling menunggu garpu di sebelahnya, dan ilmuwan sebelahnya menunggu garpu sebelahnya lagi. Dan karena posisi duduknya melingkar, semua saling menunggu satu sama lain.

Coba ganti variabelnya dengan beberapa nilai dan amati seberapa cepat deadlock terjadi. Deadlock ditandai dengan semua ilmuwan saling menunggu satu sama lain hingga waktu time out berakhir. (Seperti pada gambar berikut).

Untuk memecahkan masalah ini, kita harus mengerti bahwa deadlock bisa terjadi jika keempat kondisi berikut ini terjadi pada saat yang sama :

  1. Saling melarang (mutual exclusion): Paling sedikit salah satu sumber daya yang digunakan objek tidak boleh digunakan bersama. Dalam hal ini, satu garpu bisa digunakan oleh dua orang ilmuwan
  2. Paling sedikit salah satu proses sedang memegang suatu sumber daya, dan di saat yang sama menunggu sumber daya lain yang dipegang oleh proses lain. Dalam hal ini, agar deadlock terjadi, seorang ilmuwan pasti sedang memegang satu garpu dan menunggu garpu lain yang dipegang oleh ilmuwan lain.
  3. Suatu sumber daya tidak bisa diambil secara paksa. Proses hanya bisa melepas sumber daya dalam kondisi normal. Ilmuwan-ilmuwan kita adalah orang yang beradab, sehingga tidak bisa merebut garpu yang sedang dipegang oleh ilmuwan lain.
  4. Lingkaran menunggu sedang terjadi, di mana proses pertama sedang menunggu satu sumber daya yang dipegang oleh proses kedua, yang juga sedang menunggu sumber daya yang dipegang oleh proses ketiga, dan seterusnya hingga proses terakhir menunggu sumber daya yang dipegang oleh proses pertama, sehingga semua proses saling menunggu satu sama lain. Pada contoh ini, lingkaran menunggu terjadi karena semua ilmuwan mengambil garpu kiri terlebih dahulu baru kemudian garpu kanan. Kita bisa memecahkan deadlock dengan membalik garpu kiri dan garpu kanan pada ilmuwan terakhir, sehingga ilmuwan terakhir akan mengambil garpu kanan terlebih dahulu, baru kemudian garpu kiri.

Karena semua kondisi di atas harus terjadi secara bersama-sama agar deadlock bisa terjadi, maka untuk mencegah terjadinya deadlock, kita harus memecah salah satu kondisi saja. Pada program ini, cara termudah adalah dengan memecah kondisi keempat. Akan tetapi ini bukan satu-satunya pemecahan, kita bisa memecahkannya dengan teknik yang lebih canggih. Untuk ini saya mereferensikan Anda pada buku-buku teknik threading tingkat lanjut untuk lebih detailnya.

Kesimpulannya, Java tidak menyediakan bantuan secara alami untuk mencegah deadlock: Anda harus menghindarinya sendiri dengan membuat program multi threading dengan lebih hati-hati.

SisipanUkuran
ilmuwanmakan.zip2.14 KB

Komentar

Ralat

Synchronization merupakan mekanisme pengaksesan suatu resource (bukan 'object' seperti yang tertulis pada comment saya diatas). Meskipun telah diralat, tetap saja ini tidak menjelaskan definisi synchronization serta mekanismenya sama sekali. Mohon maaf.

Hallo, sebenarnya sudah

Hallo, sebenarnya sudah ditulis dengan menggunakan ilustrasi kamar mandi di sini.

Sorry.. but..

.. not really !

-----------------------------------------------------------

Pada baris 1 -> "..sehingga objek lain tidak bisa mengakses objek tersebut". Ini salah !

Seharusnya -> "..sehingga Thread lain tidak bisa mengakses objek tersebut".

COMMENT :

- Synchronization merupakan suatu mekanisme pengaksesan suatu object. Suatu object dapat diakses secara synchron atau usynchron.

Pertanyaannya adalah "SIAPA YANG MENGAKSES OBJECT ?"

Yang mengakses Object tidak lain adalah Thread.

Tidak pernah ada Object mengakses Object. Yang ada adalah suatu class|object|method 'calling' (memanggil) suatu class|object|method lain.

Hati2 dalam penggunaan kata|bahasa.

-----------------------------------------------------------

Pada baris 2 -> 'KUNCI'

COMMENT :

'Kunci' ? Key ? Or Lock ?

Jangan terjemahkan kata 'LOCK' dengan 'KUNCI' karena 'KUNCI' jika di bahasa Inggriskan maka jadinya 'KEY'. Sedangkan kata 'KEY' dan 'LOCK' di bahasa Inggris memiliki definisi yang berbeda karena 'KEY' adalah kata benda sedangkan 'LOCK' adalah kata kerja.

-------------------------------------------------------------------------------------------------------------------------

jika ada 'LOCK' di JAVA, maka ada 'BLOCK' di JAVA.

jika ada 'LOCKING', di JAVA maka ada 'BLOCKING' di JAVA.

jika ada 'KEY' di JAVA, ini namanya NGACO ! (kecuali interface java.security.Key)

-------------------------------------------------------------------------------------------------------------------------

Saran :

kata 'LOCK' mungkin dapat diganti dengan kata 'PENGUNCIAN' sehingga tidak berubah maknanya.

Jangan terjemahkan seluruh istilah JAVA, istilah pemprograman, atau istilah komputer 100% ke bahasa Indonesia. Jangan menghalalkan segala cara untuk membuat pembaca faham, malah jadinya bertambah bingung.

/////////////////////////////////////////////////////////////////////////////////////////////////////////////

Pada bagian 'Kerjasama Antar Thread' tertulis : "Setiap object memiliki kunci". Mungkin yang dimaksud Kunci disini tidak lain adalah 'Object Monitor'. Tidak masalah jika 'Object Monitor' di ibaratkan seperti 'KUNCI' yang mana ANAK KUNCI nya dipegang oleh suatu Thread pada suatu pengaksesan secara SYNCHRON. Tapi jika kata 'LOCK' diterjemahkan 'KUNCI', ini tidak masuk akal.

/////////////////////////////////////////////////////////////////////////////////////////////////////////////

Btw, mengapa definisi DEADLOCK tidak ditulis ? Kasus DEADLOCK ada yang sederhana dan ada yang rumit.

Btw, kalo ingin menjelaskan pengertian DEADLOCK tidak perlu menggunakan contoh kasus yang serumit itu.

Malah membuat pembaca bingung.

Terima kasih atas inputnya.

Terima kasih atas inputnya. Saya coba perbaiki mana yang salah tapi juga saya mau sedikit komentari komentar Anda.

1. Objek mengakses objek. Pada Java, thread sendiri adalah suatu objek. Thread sendiri adalah suatu unit proses, objek berada di dalam thread. Ibaratnya thread adalah baskom dan objek ada di dalam baskom. It doesn't make sense kalo baskom mengakses objek. Yang ada suatu objek mengakses objek lain via thread, either the same thread or different one. Mengakses bisa memanggil method, mengambil isi variabel, atau mengubah isi variabel suatu objek.

2. Saya setuju jika kunci dalam bahasa Inggris bisa berarti Key atau Lock. Ini karena ambigu dalam bahasa Indonesia sendiri. Kebanyakan orang Indonesia menganggap kunci adalah anak kunci, yaitu sesuatu yang bisa dibawa di dalam kantong (i.e. kunci mobil, kunci rumah). Kita juga menganggap kunci yang menempel di pintu (tempat memasukkan anak kunci) disebut kunci. Sedangkan dalam bahasa inggris, key dan lock adalah dua barang berbeda yang saling berpasangan. Dalam bahasa Inggris, lock tidak mungkin masuk kantong kecuali lock untuk koper.

Lock dan key sendiri bukan hanya kata kerja atau kata benda. Lock bisa berupa kata kerja atau kata benda. Demikian juga key, bisa kata kerja atau kata benda. Silakan lihat artinya sendiri di http://dictionary.reference.com/browse/lock dan http://dictionary.reference.com/browse/key

Tapi "kunci dilepas" sudah pasti "lock is released", bukan "key is released", karena makna yang kedua kuncinya dijatuhkan, which doesn't make sense. Lock sendiri bukan cuma kata kerja, tapi juga kata benda, yang artinya bisa kunci atau gembok. Sedangkan key artinya anak kunci. Key ngga pernah nyantel di garasi, tapi nyantel di gembok yang nyantel di garasi. Sementara "key" untuk security itu persis artinya anak kunci. Karena anak kunci ini digunakan untuk membuka kotak (atau pesan) rahasia.

Anyway, saya coba perbaiki lagi tata bahasanya untuk masalah kunci ini. Memang agak susah jika kita belajar Java via bahasa Inggris atau bahasa Indonesia campur Inggris jadinya runyam. Apalagi bahasa Indonesia sangat terbatas dibandingkan bahasa Inggris.

3. Definisi deadlock sudah diilustrasikan di paragraf pertama.

Terima kasih untuk inputnya, semoga menjelaskan apa yang saya maksud.