Senin, 03 Maret 2014

Kerja Sama Antar Thread

Setelah kita mengerti bagaimana thread bisa bertabrakan satu sama lain, dan bagaimana caranya mencegah tabrakan antar thread, langkah berikutnya adalah belajar bagaimana membuat thread dapat bekerja sama satu sama lain. Kuncinya adalah komunikias antar thread yang diimplementasi dengan aman dalam metode-metode pada kelas Object, yaitu wait() dan notify().
wait() dan notify()
Pertama-tama penting untuk mengerti bahwa sleep() tidak melepas kunci thread ketika dipanggil. Artinya jika sleep() dipanggil dari dalam bagian kritis, maka thread lain tidak bisa masuk hingga thread yang memanggil sleep() bangun, meneruskan eksekusi, hingga keluar dari bagian kritis. Sedangkan wait() melepas kunci ketika dipanggil, sehingga thread lain bisa masuk ke dalam bagian kritis.
Ada dua bentuk wait(). Yang pertama memiliki argumen waktu dalam bentuk mili detik (mirip dengan sleep(). Perbedaannya dengan sleep() adalah :
  • wait() melepaskan kunci
  • Kita bisa membatalkan wait() dengan menggunakan notify() atau notifyAll(), atau hingga waktu tunggu berlalu.
Bentuk kedua dari wait() adalah wait() yang tidak memiliki argumen. Jenis wait() ini akan terus berlangsung hingga dibatalkan dengan notify atau notifyAll().
Aspek penting dari wait(), notify() dan notifyAll() adalah metode ini merupakan bagian dari kelas dasar Obejct dan bukan bagian dari kelas Thread seperti sleep(). Meskipun kelihatan janggal, hal ini sangat penting karena semua objek memiliki kunci. Artinya kita bisa memanggil wait() dari dalam metode synchronized, tidak peduli apakah kelas tersebut merupakan kelas turunan dari Thread atau bukan.
Sebetulnya satu-satunya tempat kita bisa memanggil wait(), notify() dan notifyAll() adalah dari dalam blok atau metode synchronized. (sleep() bisa dipanggil dari manapun karena ia tidak berhubungan dengan kunci suatu objek). Jika kita memanggil wait(), notify() atau notifyAll() dari luar metode atau blok synchronized, compiler tidak akan memperingatkan Anda, akan tetapi ketika program dijalankan, kita akan mendapatkan pengecualian IllegalMonitorStateException dengan pesan kesalahan yang tidak dimengerti, seprti "thread ini bukan pemiliknya". Pesan ini berarti bahwa thread yang memanggil wait(), notify() atau notifyAll() harus memiliki kunci objek sebelum bisa memanggil salah satu metode ini.
Kita juga bisa meminta suatu objek untuk memanipulasi kuncinya sendiri. Caranya, pertama-tama kita harus mengambil kuncinya. Misalnya, jika kita ingin memanggil notify() ke suatu objek x, kita harus melakukannya di dalam blok synchronized untuk mengambil kunci x, seperti :
synchronized(x) {
    x.notify();
}
Biasanya, wait() digunakan jika kita menunggu sesuatu yang dikontrol oleh sesuatu di luar kontrol metode kita (di mana sesuatu ini hanya bisa diubah oleh thread lain). Kita tidak ingin menunggu dan berulang-ulang menguji apakah sesuatu itu sudah tersedia, karena cara ini akan memboroskan penggunaan CPU. Kita bisa menggunakan wait() untuk memerintahkan suatu thread untuk menunggu hingga sesuatu tersebut berubah, dan hanya ketika notify() dipanggil, maka thread tersebut akan bangun dan mengeceknya. Dengan kata lain wait() digunakan melakukan aktifitas tak-sinkron antara beberapa thread.
Sebagai contoh, anggap suatu restoran memiliki satu orang koki dan satu orang pelayan. Pelayan harus menunggu hingga si koki selesai memasak makanan. Ketika koki selesai, ia akan memberi tahu pelayan, kemudian membawa makanan ini ke customer, kemudian menunggu kembali. Koki di sini kita sebut sebagai produsen, dan pelayan disebut sebagai konsumen.
package com.lyracc.rumahmakan;
 
class Pesanan {
    private int i = 0;
 
    public Pesanan(int i) {
        this.i = i;
    }
 
    public String toString() {
        return "pesanan " + i;
    }
} // akhir kelas Pesanan
 
class Pelayan extends Thread {
    private RumahMakan rumahMakan;
 
    public Pelayan(RumahMakan r) {
        rumahMakan = r;
        start();
    }
 
    public void run() {
        while (true) {
            while (rumahMakan.pesanan == null)
                // tunggu hingga dipanggil dengan notify oleh Koki
                synchronized (this) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            System.out.println("Pelayan mengantarkan " + rumahMakan.pesanan);
            // pesanan sudah diantar, pesanan sekarang kosong
            rumahMakan.pesanan = null;
        }
    }
} // akhir kelas Pelayan
 
class Koki extends Thread {
    private RumahMakan rumahMakan;
    private Pelayan pelayan;
 
    public Koki(RumahMakan r, Pelayan p) {
        rumahMakan = r;
        pelayan = p;
        start();
    }
 
    public void run() {
        // masak 10 makanan
        for (int i = 0; i < 10; i++) {
            if (rumahMakan.pesanan == null) {
                rumahMakan.pesanan = new Pesanan(i);
                System.out.print("Pesanan selesai! ");
                // coba panggil pelayan jika tidak sibuk
                synchronized (pelayan) {
                    pelayan.notify();
                }
            }
            try {
                sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("Makanan habis..");
        System.exit(0);
    }
} // akhir kelas Koki
 
public class RumahMakan {
    Pesanan pesanan;
 
    public static void main(String[] args) {
        RumahMakan rumahMakan = new RumahMakan();
        Pelayan pelayan = new Pelayan(rumahMakan);
        new Koki(rumahMakan, pelayan);
    }
}
Keluarannya ada sebagai berikut.
<img src="/sites/java.lyracc.com/files/kerjasamathread_gbr1.png" alt="" />
Pesanan adalah kelas sederhana yang berisi pesanan. Konstruktor menerima angka yang diibaratkan seperti pesanan, kemudian membebanlebihkan metode toString() untuk mencetak objek ini langsung dengan System.out.println().
Seorang Pelayan harus tahu RumahMakan tempat ia bekerja, karena ia harus ke sana untuk mengantarkan pesanan dari "jendela pemesanan", yaitu rumahMakan.pesanan. Pada metode run(), Pelayan masuk dalam mode menunggu. Kuncinya dimiliki oleh pelayan ini sendiri. Kunci ini yang akan digunakan oleh Koki untuk membangunkan Pelayan jika makanan sudah siap dengan metode notify().
Pada aplikasi yang lebih kompleks, misalnya jika pelayannya banyak, kita bisa memanggil notifyAll() untuk membangunkan semua pelayan. Setiap pelayan nanti akan menguji apakah panggilan itu untuknya atau tidak.
Perhatikan bahwa wait() ditulis di dalam pernyataan while untuk menguji apakah pesanan sudah datang. Mungkin ini agak terasa ganjil karena ketika thread ini dibangunkan ketika menunggu pesanan, seharusnya pesanannya sudah tersedia khan? Masalahnya jika aplikasinya terdiri dari banyak pelayan, thread lain mungkin sudah keburu mengantarkan pesanannya ketika thread ini sedang bangun. Untuk itu, lebih aman apabila kita menggunakan bentuk berikut untuk semua aplikasi yang menggunakan wait() :
while (sesuatuYangDitunggu)
    wait();
Dengan melakukan ini, kita menjamin bahwa kondisi di atas akan terpenuhi sebelum thread mendapatkan sesuatu yang ditunggu. Jika thread sudah dibangunkan akan tetapi pesanan tidak ada, maka thread akan kembali tidur.
Objek Koki harus tahu di rumah makan mana ia bekerja. Pesanan yang dia masak akan dia letakkan pada jendela pesanan (dalam hal ini rumahMakan.pesanan) dan dia juga harus tahu siapa Pelayan yang akan mengantarkan pesanan yang sudah selesai dimasak.
Pada contoh sederhana di atas, Koki membuat objek Pesanan, kemudian setelah selesai akan memanggil Pelayan dengan notify(). Karena panggilan notify() dilakukan di dalam klausa synchronized, maka sudah bisa dipastikan Koki memanggil pelayan jika pelayan tersebut sedang tidak digunakan oleh thread lain.

Sumber : http://java.lyracc.com/belajar/java-untuk-pemula/kerjasama-antar-thread

Tidak ada komentar:

Posting Komentar