Sunday, March 25, 2018

11. Pemrograman Multithread Bagian Dua



Memilih Pendekatan
Pada titik ini, Anda mungkin bertanya mengapa Java memiliki dua cara untuk menciptakan thread anak, dan mana pendekatan yang lebih baik. Jawaban terhadap pertanyaan ini akan dijelaskan berikut. Kelas Thread mendefinisikan beberapa metode yang dapat didefinisikan ulang oleh subkelas. Dari metode-metode ini, satu-satunya yang harus didefinisikan ulang adalah run(). Jadi, tentu saja, metode yang sama diperlukan ketika Anda mengimplementasikan Runnable. Banyak programer Java merasa bahwa kelas seharusnya diwarisi hanya ketika kelas itu perlu ditingkatkan kemampuannya. Jadi, jika Anda tidak perlu mendefinisikan ulang metode-metode lain dari kelas Thread, maka direkomendasikan bahwa cara terbaiknya adalah dengan mengimplementasikan antarmuka Runnable saja. Selain itu, dengan mengimplementasikan Runnable, kelas thread Anda tidak perlu mewarisi Thread, yang membuatnya bebas untuk mewarisi kelas lain yang berbeda. Tentu saja, pilihan pendekatan ini tergantung kebutuhan Anda. Namun, pada buku ini, penciptaan thread baru dilakukan dengan mengimplementasikan antarmuka Runnable.


Menciptakan Thread Jamak
Sejauh ini, Anda telah menggunakan hanya dua thread: thread utama dan satu thread anak. Sebenarnya, program Anda dapat melahirkan sebanyak mungkin thread yang diperlukan. Misalnya, program berikut menciptakan tiga thread anak:

// Menciptakan thread jamak.
class ThreadBaru implements Runnable {
   String nama; // nama dari thread
   Thread t;

   ThreadBaru(String namaThread) {
      nama = namaThread;
      t = new Thread(this, nama);

      System.out.println("Thread Baru: " + t);
      t.start(); // Mangawali thread
   }

   // Ini titik masuk untuk thread.
   public void run() {
      try {
         for(int i = 5; i > 0; i--) {
            System.out.println(nama + ": " + i);
            Thread.sleep(1000);
         }
      } catch (InterruptedException e) {
         System.out.println(nama + " diinterupsi");
      }

      System.out.println(nama + " selesai dieksekusi.");
   }
}

public class DemoThreadJamak {
   public static void main(String args[]) {
      new ThreadBaru("Satu"); // mengawali tiga thread
      new ThreadBaru("Dua");
      new ThreadBaru("Tiga");
  
      try {
         // menunggu untuk thread lain berakhir
         Thread.sleep(10000);
      } catch (InterruptedException e) {
         System.out.println("Thread Utama diinterupsi");
      }
  
      System.out.println("Thread Utama selesai dieksekusi.");
   }
}

Contoh keluaran program ditunjukkan di sini (dapat berbeda tergantung dengan kondisi CPU yang sedang Anda gunakan):

gunakan):

Thread Baru: Thread[Satu,5,main]
Satu: 5
Thread Baru: Thread[Dua,5,main]
Thread Baru: Thread[Tiga,5,main]
Dua: 5
Tiga: 5
Satu: 4
Dua: 4
Tiga: 4
Dua: 3
Satu: 3
Tiga: 3
Satu: 2
Dua: 2
Tiga: 2
Satu: 1
Dua: 1
Tiga: 1
Dua selesai dieksekusi.
Tiga selesai dieksekusi.
Satu selesai dieksekusi.
Thread Utama selesai dieksekusi.

Anda bisa memodifikasinya juga menjadi seperti ini, sehingga masing-masing thread anak memiliki operasi atau pekerjaan yang berbeda:

// Menciptakan thread jamak.

//Menciptakan thread anak 1
class ThreadBaru1 implements Runnable {
   String nama; // nama dari thread
   Thread t;

   ThreadBaru1(String namaThread) {
      nama = namaThread;
      t = new Thread(this, nama);

      System.out.println("Thread Anak 1: " + t);
      t.start(); // Mangawali thread
   }

   // Ini titik masuk untuk thread anak 1.
   public void run() {
      try {
         for(int i = 5; i > 0; i--) {
            System.out.println(nama + ": " + i);
            Thread.sleep(1000);
         }
      } catch (InterruptedException e) {
         System.out.println(nama + " diinterupsi");
      }

      System.out.println(nama + " selesai dieksekusi.");
   }
}

//Menciptakan thread anak 2
class ThreadBaru2 implements Runnable {
 String nama; // nama dari thread
 Thread t;

 ThreadBaru2(String namaThread) {
    nama = namaThread;
    t = new Thread(this, nama);

    System.out.println("Thread Anak 2: " + t);
    t.start(); // Mangawali thread
 }

 // Ini titik masuk untuk thread anak 2.
 public void run() {
    try {
       for(int i = 0; i < 10; i+=2) {
          System.out.println(nama + ": " + i);
          Thread.sleep(1000);
       }
    } catch (InterruptedException e) {
       System.out.println(nama + " diinterupsi");
    }

    System.out.println(nama + " selesai dieksekusi.");
 }
}

//Menciptakan thread anak 3
class ThreadBaru3 implements Runnable {
String nama; // nama dari thread
Thread t;

ThreadBaru3(String namaThread) {
  nama = namaThread;
  t = new Thread(this, nama);

  System.out.println("Thread Anak 3: " + t);
  t.start(); // Mangawali thread
}

// Ini titik masuk untuk thread anak 3.
public void run() {
  try {
     for(int i = 0; i < 5; i++) {
        System.out.println(nama + ": " + i);
        Thread.sleep(1000);
     }
  } catch (InterruptedException e) {
     System.out.println(nama + " diinterupsi");
  }

  System.out.println(nama + " selesai dieksekusi.");
}
}

public class DemoThreadJamak2 {
   public static void main(String args[]) {
      new ThreadBaru1("Anak Satu"); // mengawali tiga thread
      new ThreadBaru2("Anak Dua");
      new ThreadBaru3("Anak Tiga");
  
      try {
         // menunggu untuk thread lain berakhir
         Thread.sleep(10000);
      } catch (InterruptedException e) {
         System.out.println("Thread Utama diinterupsi");
      }
  
      System.out.println("Thread Utama selesai dieksekusi.");
   }
}

Contoh keluaran program ditunjukkan di sini (dapat berbeda tergantung dengan kondisi CPU yang sedang Anda gunakan):

Thread Anak 1: Thread[Anak Satu,5,main]
Thread Anak 2: Thread[Anak Dua,5,main]
Anak Satu: 5
Anak Dua: 0
Thread Anak 3: Thread[Anak Tiga,5,main]
Anak Tiga: 0
Anak Tiga: 1
Anak Satu: 4
Anak Dua: 2
Anak Dua: 4
Anak Satu: 3
Anak Tiga: 2
Anak Dua: 6
Anak Tiga: 3
Anak Satu: 2
Anak Dua: 8
Anak Satu: 1
Anak Tiga: 4
Anak Satu selesai dieksekusi.
Anak Tiga selesai dieksekusi.
Anak Dua selesai dieksekusi.
Thread Utama selesai dieksekusi.


Menggunakan isAlive() dan join()
Seperti disebutkan, adakalanya Anda menginginkan thread utama untuk berakhir terakhir. Pada contoh-contoh sebelumnya, ini dilakukan dengan memanggil sleep() di dalam main(), dengan sebuah tunda yang cukup panjang untuk memastikan bahwa semua thread anak berhenti sebelum thread utama berakhir. Namun, ini bukanlah solusi yang memuaskan. Anda mungkin bisa bertanya bagaimana satu thread dapat mengetahui kapan thread lain berakhir. Untunglah, kelas Thread menyediakan caranya.

Ada dua cara dalam menentukan kapan sebuah thread berakhir. Pertama, Anda dapat memanggil metode isAlive() pada thread tersebut. Metode ini didefinisikan oleh Thread, dan memiliki bentuk umumnya yang ditunjukkan di sini:

final boolean isAlive()

Metode isAlive() menghasilkan true jika thread dimana ia dipanggil masih berjalan. Sebaliknya, ia menghasilkan false.

Metode yang paling umum digunakan untuk menunggu sebuah thread untuk berakhir dinamakan dengan join(), seperti ditunjukkan di sini:

final void join() throws InterruptedException

Metode ini menunggu sampai thread di mana ia dipanggil berakhir. Berikut adalah versi diperbaiki dari contoh sebelumnya yang menggunakan join() untuk memastikan bahwa thread utama adalah thread yang terakhir berhenti. Program juga mendemonstrasikan metode isAlive():

class ThreadBaru implements Runnable {
   String nama; // nama dari thread
   Thread t;

   ThreadBaru(String namaThread) {
      nama = namaThread;
      t = new Thread(this, nama);

      System.out.println("Thread Baru: " + t);
      t.start(); // Mangawali thread
   }

   // Ini titik masuk untuk thread.
   public void run() {
      try {
         for(int i = 5; i > 0; i--) {
            System.out.println(nama + ": " + i);
            Thread.sleep(1000);
         }
      } catch (InterruptedException e) {
         System.out.println(nama + " diinterupsi");
      }

      System.out.println(nama + " selesai dieksekusi.");
   }
}

public class DemoJoin {
   public static void main(String args[]) {
      ThreadBaru ob1 = new ThreadBaru("Satu"); // mengawali tiga thread
      ThreadBaru ob2 = new ThreadBaru("Dua");
      ThreadBaru ob3 = new ThreadBaru("Tiga");
  
      System.out.println("Thread Satu masih berjalan?: " + ob1.t.isAlive());
      System.out.println("Thread Dua masih berjalan?: "  + ob2.t.isAlive());
      System.out.println("Thread Tiga masih berjalan?: " + ob3.t.isAlive());
        
      // Menunggu thread berakhir
      try {
         System.out.println("Waiting for threads to finish.");
         ob1.t.join();
         ob2.t.join();
         ob3.t.join();
      } catch (InterruptedException e) {
         System.out.println("Main thread Interrupted");
      }
          
      System.out.println("Thread Satu masih berjalan?: " + ob1.t.isAlive());
      System.out.println("Thread Dua masih berjalan?: "  + ob2.t.isAlive());
      System.out.println("Thread Tiga masih berjalan?: " + ob3.t.isAlive());
      System.out.println("Thread Utama selesai dieksekusi.");
   }
}

Contoh keluaran program ditunjukkan di sini (dapat berbeda tergantung dengan kondisi CPU yang sedang Anda gunakan):

Thread Baru: Thread[Satu,5,main]
Thread Baru: Thread[Dua,5,main]
Satu: 5
Thread Baru: Thread[Tiga,5,main]
Thread Satu masih berjalan?: true
Thread Dua masih berjalan?: true
Thread Tiga masih berjalan?: true
Waiting for threads to finish.
Tiga: 5
Dua: 5
Satu: 4
Tiga: 4
Dua: 4
Dua: 3
Tiga: 3
Satu: 3
Tiga: 2
Satu: 2
Dua: 2
Dua: 1
Tiga: 1
Satu: 1
Tiga selesai dieksekusi.
Satu selesai dieksekusi.
Dua selesai dieksekusi.
Thread Satu masih berjalan?: false
Thread Dua masih berjalan?: false
Thread Tiga masih berjalan?: false
Thread Utama selesai dieksekusi.


Sinkronisasi
Ketika dua atau lebih thread memerlukan akses terhadap sumberdaya berbagi-pakai, Anda memerlukan cara untuk memastikan bahwa sumberdaya tersebut hanya dipakai oleh satu thread pada satu waktu. Proses ini dikenal dengan sinkronisasi. Seperti yang akan Anda lihat, Java menawarkan solusi unik pada permasalahan ini.

Hal kunci pada sinkronisasi adalah konsep monitor. Monitor adalah sebuah objek yang dipakai sebagai penguncian eksklusif mutual. Hanya satu thread yang dapat memiliki sebuah monitor pada satu waktu. Ketika sebuah thread melakukan penguncian, dikatakan bahwa thread itu memiliki monitor terkunci. Semua thread lain yang mencoba untuk memiliki monitor terkunci akan ditunda sampai thread pemilik monitor terkunci itu selesai dieksekusi. Sebuah thread yang memiliki monitor dapat memasuki atau memiliki kembali monitor tersebut jika diinginkan.

Anda bisa mensinkronisasi kode Anda dengan dua cara. Kedua cara tersebut melibatkan penggunaan katakunci synchronize, dan keduanya akan dipelajari di sini.


Menggunakan Metode Sinkronisasi
Sinkronisasi dalam Java cukup mudah dilakukan, karena semua objek memiliki monitor implisit sendiri. Untuk memasuki atau memiliki monitor dari sebuah objek, Anda hanya perlu memanggil sebuah metode yang telah dideklarasikan dengan katakunci synchronize. Ketika sebuah thread berada di dalam sebuah metode tersinkron, semua thread lain yang mencoba memanggilnya pada objek yang sama harus menunggu. Untuk keluar dari monitor dan menyerahkan kendali objek kepada thread penunggu selanjutnya, pemilik monitor hanya perlu keluar dari metode tersinkron.

Untuk memahami proses sinkronisasi, Anda akan mempelajari sebuah contoh sederhana. Program berikut memiliki tiga kelas sederhana. Kelas pertama, PanggilAku, memiliki sebuah metode dengan nama panggil(). Metode panggil() mengambil sebuah parameter String dengan nama psn. Metode ini mencoba menampilkan string psn di dalam kurung siku. Hal menarik untuk diperhatikan di sini adalah bahwa setelah panggil() menampilkan kurung siku pembuka dan string psn, ia memanggil Thread.sleep(1000), yang menunda thread tersebut selama satu detik.

Konstruktor dari kelas berikutnya, Pemangil, mengambil sebuah referensi yang menunjuk ke sebuah objek dari kelas PanggilAku dan sebuah String, yang berturut-turut disimpan ke dalam target dan psn. Konstruktor ini juga menciptakan sebuah thread baru yang akan memanggil metode run(). Thread ini segera dimulai atau dieksekusi. Metode run() dari Pemanggil() memangil metode panggil() pada objek target dari PanggilAku, yang melewatkan string psn. Terakhir, kelas Sinkron menciptakan sebuah objek dari PanggilAku, dan tiga objek dari Pemanggil, masing-masing dengan pesan unik. Objek yang sama dari PanggilAku dilewatkan kepada tiap Pemanggil.

// Program ini menggunakan sebuah blok sinkronisasi.
class PanggilAku {
   void panggil(String msg) {
      System.out.print("[" + msg);
      
      try {
         Thread.sleep(1000);
      } catch (InterruptedException e) {
         System.out.println("Diinterupsi");
      }
 
      System.out.println("]");
   }
} 

class Pemanggil implements Runnable {
   String psn;
   PanggilAku target;
   Thread t;

   public Pemanggil(PanggilAku targ, String s) {
      target = targ;
      psn = s;
      
      t = new Thread(this);
      t.start();
   }

   public void run() {
         target.panggil(psn);
   }
}

public class Sinkron {
   public static void main(String args[]) {
      PanggilAku target = new PanggilAku();
  
      Pemanggil ob1 = new Pemanggil(target, "Halo");
      Pemanggil ob2 = new Pemanggil(target, "Dunia");
      Pemanggil ob3 = new Pemanggil(target, "Sinkronisasi");
  
      // Menunggu thread-thread berakhir
      try {
         ob1.t.join();
         ob2.t.join();
         ob3.t.join();
      } catch(InterruptedException e) {
         System.out.println("Diinterupsi");
      }
   }
}

Jika dijalankan, program ini akan menghasilkan contoh keluaran berikut:

[Halo[Sinkronisasi[Dunia]
]
]

Seperti yang dapat Anda lihat, dengan memanggil sleep(), metode panggil() membiarkan eksekusi bergeser ke thread lain. Ini menyebabkan keluaran bercampur dari ketiga string pesan. Pada program ini, tidak ada yang menghentikan ketiga thread dari pemanggilan terhadap metode yang sama, pada objek yang sama, pada waktu yang sama. Ini dikenal sebagai kondisi kompetisi, karena ketiga thread saling beradu kecepatan untuk mengeksekusi metode tersebut (panggil()). Contoh ini menggunakan sleep() untuk menunjukkannya. Pada banyak kejadian, kondisi kompetisi lebih rumit dan susah diprediksi, karena Anda tidak bisa memastikan kapan penukaran eksekusi akan terjadi.


Selanjutnya  >>>

No comments: