Minggu, 18 November 2012

Dasar dasar Thread





Hallo ketemu lagi dengan saya,kali ini saya akan  melanjutkan postingan saya tentang pemrograman serentak,kali ini saya akan membahas tentang dasar dasar thread

Cara termudah untuk membuat thread adalah membuat kelas turunan dari java.lang.Thread, yang memiliki semua metode untuk membuat dan menjalankan thread. Metode paling penting adalah run(), yang bisa kita beban-lebihkan untuk melakukan tugas yang kita butuhkan. Atau dengan kata lain run() adalah metode yang akan dijalankan bersamaan dengan thread lain.
Contoh berikut membuat 5 thread, masing-masing memiliki nomor identifikasi unik yang dibuat dengan menggunakan variabel statik. Metode run() dibebanlebihkan untuk menghitung mundur hingga hitungMundur bernilai nol. Setelah metode run() selesai dijalankan, thread akan mati secara otomatis.




Pada contoh program di atas, objek thread diberi nama melalui argumen pada konstruktornya. Nama ini dipanggil ketika metode run() melakukan penghitungan mundur, yaitu dengan menggunakan metode getName().
Metode run() pada thread biasanya memiliki perulangan internal yang akan terus menerus dipanggil hingga tidak lagi digunakan. Kita harus membuat suatu kondisi sehingga bisa keluar dari perulangan tersebut (misalnya pada contoh di atas, perulangan akan selesai jika hitungMundur bernilai 0). Seringkali, run() dijalankan di dalam perulangan yang tak pernah berhenti (kita akan lihat nanti bagaimana menghentikan suatu thread dengan aman).
Pada metode main(), thread dibuat beberapa kali kemudian dijalankan. Metode start() pada kelas Thread digunakan untuk melakukan tugas tertentu sebelum metode run() dijalankan. Jadi, langkah-langkahnya adalah : konstruktor dipanggil untuk membuat objek, kemudian memanggil start() untuk melakukan konfigurasi thread, dan kemudian metode run() dijalankan. Jika kita tidak memanggil start() maka metode run() tidak akan pernah dijalankan.
Keluaran dari program ini akan berbeda setiap kali dijalankan, karena penjadwalan thread tidak dapat ditentukan dengan pasti (non-deterministik). Bahkan, kita bisa melihat perbedaan yang sangat jelas ketika kita menggunakan versi JDK yang berbeda. Misalnya, JDK lama tidak melakukan pembagian waktu lebih cepat, artinya, 1 thread mungkin bisa melakukan tugasnya dengan cepat hingga selesai sebelum thread lain dijalankan. Pada JDK lain kita akan melihat program akan mencetak 5 untuk seluruh thread hingga 1 untuk seluruh thread. Artinya pembagian waktunya lebih baik, karena setiap thread memiliki kesempatan yang sama untuk menjalankan program. Karenanya, untuk membuat suatu program multi-threading, kita tidak boleh terpaku pada keluaran suatu kompiler. Program kita harus dibuat seaman mungkin.
Ketika objek Thread dibuat pada metode main(), kita lihat bahwa kita tidak menyimpan referensi ke objek tersebut. Pada objek biasa, tentunya objek ini akan langsung ditangkap oleh pemulung memori karena objek ini tidak direferensikan di manapun. Akan tetapi pada thread, objek hanya bisa diambil oleh pemulung memori jika metode run() selesai dijalankan. Pada contoh di atas, program masih bisa berjalan seperti biasa, dan objek Thread akan diberikan kepada pemulung memori setelah mencetak angka 1.

Yielding (menghasilkan)
Jika kita tahu bahwa kita telah mendapatkan hasil yang kita inginkan pada metode run(), kita bisa memberi tahu penjadwal thread bahwa kita telah selesai dan memberi jalan kepada thread lain untuk mendapatkan kesempatan pada CPU. Akan tetapi ini hanya sebagai petunjuk, yang artinya belum tentu dijalankan oleh penjadwal thread.
Misalnya pada contoh di atas, kita bisa mengganti isi metode run() dengan

 

Secara umum, yield mungkin berguna untuk situasi yang agak langka, dan kita tidak bisa menggunakannya secara serius untuk memperbaiki kinerja aplikasi kita.

 Tidur (sleeping)
Cara lain untuk mengatur perilaku thread kita adalah dengan memanggil sleep untuk menunda eksekusi thread selama waktu tertentu (dalam mili detik). Misalnya pada kode berikut, kita ubah metode run() menjadi seperti :

 

Ketika kita memanggil sleep(), metode ini harus diletakkan di dalam blok try karena sleep() bisa melemparkan pengecualian, yaitu jika tidurnya diganggu sebelum waktunya selesai. Hal ini terhadi misalnya apabila thread lain yang memiliki referensi ke thread ini memanggil interrupt() pada thread ini. Pada contoh di atas, kita lemparkan lagi pengecualian yang terjadi dengan pengecualian lain bertipe RuntimeException, karena kita tidak tahu bagaimana pengecualian ini harus ditangani, dan membiarkan metode yang memanggilnya menangkap pengecualian baru ini.
Metode sleep() tidak digunakan untuk mengatur bagaimana thread akan berjalan menurut urutan tertentu. Metode ini hanya menghentikan eksekusi suatu thread sementara. Yang dijamin adalah bahwa thread akan tidur selama paling sedikit 100 mili detik (atau mungkin sedikit lebih lama hingga thread jalan kembali). Urutan thread diatur oleh penjadwal thread yang memiliki mekanisme sendiri tergantung dari keadaan thread lain atau bahkan aplikasi lain di luar Java, oleh karena itu sifatnya disebut non-deterministik.
Jika kita harus mengatur thread mana dahulu yang harus dijalankan, cara terbaik mungkin tidak menggunakan thread sama sekali, atau mendesain agar suatu thread memanggil thread lain dengan suatu urutan tertentu. Tentunya cara terakhir lebih rumit dari yang dibayangkan.
Prioritas
Prioritas suatu thread digunakan untuk memberi tahu penjadwal thread tentang prioritas thread tersebut. Tetap saja urutannya tidak bisa ditentukan karena sifatnya yang non-deterministik. Jika ada beberapa thread yang sedang diblok dan menunggu giliran untuk dijalankan, penjadwal thread akan cenderung menjalankan thread dengan prioritas tertinggi terlebih dahulu. Akan tetapi, tidak berarti thread dengan prioritas rendah tidak akan pernah dijalankan, hanya lebih jarang dijalankan ketimbang thread dengan prioritas tinggi.
Perhatikan contoh berikut :

 

Pada contoh di atas, kita ubah konstruktornya untuk mengeset prioritas kemudian menjalankan thread. Pada metode main() kita buat 6 thread, yang pertama dengan prioritas maximum, dan yang lain dengan prioritas minimum. Perhatikan keluarannya, bagaimana thread pertama dijalankan lebih dulu sedangkan thread-thread lain berjalan seperti biasa dalam kondisi acak karena memiliki prioritas yang sama.
Di dalam metode run() kita lakukan perhitungan matematika selama 100.000 kali. Tentunya ini perhitungan yang memakan waktu sehingga setiap thread harus menunggu giliran di saat thread lain sedang dijalankan. Tanpa perhitungan ini, thread akan dilaksanakan sangat cepat dan kita tidak bisa melihat efek dari prioritas thread.
Prioritas suatu thread bisa kita set kapan saja (tidak harus pada konstruktor) dengan metode setPriority(int prioritas) dan kita bisa membaca prioritas suatu thread dengan menggunakan metode getPriority().
Meskipun JDK memiliki 10 tingkat prioritas, akan tetapi sistem operasi memiliki tingkat prioritas yang berbeda-beda. Windows misalnya memiliki 7 tingkat dan Solaris memiliki 231 tingkat prioritas. Yang lebih pasti adalah menggunakan konstanta MAX_PRIORITY, NORM_PRIORITY, dan MIN_PRIORITY pada kelas thread.

Thread Daemon
Thread daemon adalah thread yang bekerja di belakang layar yang memberikan layanan umum kepada thread-thread lain selama program berjalan, akan tetapi thread ini bukan bagian penting dari suatu program. Artinya ketika semua thread yang bukan daemon selesai dijalankan, program akan berhenti, dan jika masih ada thread non-daemon yang masih dieksekusi, program tidak akan berhenti.
Perhatikan contoh program berikut ini.

 

Perintah setDaemon() sebelum metode start() dipanggil. Pada metode run(), thread diperintahkan untuk tidur selama 100 mili detik. Ketika semua thread dimulai, program langsung berhenti sebelum thread bisa mencetak dirinya. Ini karena semua thread kecuali main() adalah thread daemon. Hanya thread non-daemon saja yang bisa mencegah program untuk terus berjalan.
Untuk mengetahui suatu thread adalah thread daemon atau bukan, kita bisa menggunakan perintah isDaemon(). Suatu thread daemon akan membuat thread yang juga merupakan thread daemon.

Menggabungkan thread
Perintah join() bisa digunakan pada thread lain untuk menunda eksekusi hingga thread lain tersebut selesai dijalankan. Misalnya, jika thread a memanggil t.join() pada thread t, maka eksekusi thread a akan terhenti sementara hingga thread t selesai dijalankan (atau ketika t.isAlive() bernilai false).
Kita bisa juga memanggil join() dengan argumen waktu (baik dalam mili detik, ataupun milidetik dan nanodetik), yaitu jika thread target tidak selesai dalam kurun waktu tersebut, eksekusi pada thread induk akan kembali dilakukan.
Panggilan join() bisa dibatalkan dengan memanggil interrupt() pada thread induk, sehingga klausa try ... catch diperlukan pada metode join().
Mari kita lihat contoh berikut ini.

 

Hasil keluarannya adalah seperti pada gambar berikut.



ThreadPemalas adalah thread yang akan ditidurkan sepanjang waktu yang diberikan pada konstruktornya. Metode run() bisa berhenti jika waktu tidur sudah habis atau ada interupsi yang terjadi. Di dalam klausa catch, interupsi akan dilaporkan. Fungsi isInterrupted() melaporkan apakah thread ini diinterupsi atau tidak. Akan tetapi ketika thread ini diinterupsi, kemudian pengecualiannya ditangkap oleh klausa catch, misalnya, maka tanda interupsi akan segera dihapus. Oleh karenanya isInterrupted() akan selalu bernilai false pada program di atas. Tanda interupsi akan digunakan pada situasi lain yang mungkin berada di luar pengecualian.
ThreadPenggabung adalah thread yang menunggu hingga ThreadPemalas selesai dengan tugasnya, yaitu dengan memanggil join() ke objek ThreadPemalas pada metode run()-nya.
Pada metode utama main(), setiap ThreadPemalas tersambung pada ThreadPenggabung. Dan kita lihat pada keluarannya, jika ThreadPemalas selesai bekerja, baik karena dibangunkan melalui interupsi atau karena waktu sudah selesai, ThreadPenggabung yang tersambung juga akan menyelesaikan tugasnya.
Variasi Kode
Pada contoh-contoh di atas, semua objek thread yang kita buat diturunkan dari kelas Thread. Kita hanya membuat objek yang berfungsi sebagai thread dan tidak memiliki tugas dan fungsi lain. Akan tetapi, kelas kita mungkin saja merupakan kelas turunan dari kelas lain. Karena Java tidak mendukung pewarisan berganda, kita tidak bisa menurunkan kelas tersebut bersamaan dengan kelas Thread.
Dalam hal ini, kita bisa menggunakan cara alternatif yaitu dengan mengimplementasi interface Runnable. Runnable hanya memiliki satu metode untuk diimplementasi, yaitu metode run().
Contoh berikut mendemonstrasikan contoh penggunaannya :

 

Satu-satunya yang dibutuhkan oleh kelas RunnableSederhana adalah metode run(), akan tetapi jika kita ingin melakukan hal lainnya, seperti getName(), sleep(), dan lainnya, kita harus secara eksplisit memberikan referensi dengan menggunakan Thread.currentThread().
Ketika suatu kelas mengimplementasikan interface Runnable, artinya kelas ini memiliki metode bernama run(), akan tetapi tidak berarti bahwa kelas ini bisa melakukan sesuatu seperti kelas Thread atau kelas-kelas turunan yang kita buat dari kelas ini. Kita harus membuat objek Thread sendiri seperti ditunjukkan dalam metode main() di atas, kemudian menjalankan start() sendiri.
Kemudahan yang ditawarkan oleh interface Runnable adalah kemungkinan untuk menggabungkannya dengan kelas dan interface lain. Misalnya kita ingin membuat kelas baru yang merupakan kelas turunan dari suatu kelas lain. Kita cukup menambahkan impement Runnable pada definisi kelasnya untuk membuat kelas yang bisa kita jadikan thread. Dengan cara ini, kita masih bisa mengakses anggota kelas induk secara langsung, tanpa melalui objek lain. Akan tetapi, kelas dalam (inner class) juga bisa mengakses anggota kelas luar (outer class). Kadang-kadang kita ingin juga membuat kelas dalam yang merupakan turunan dari kelas Thread.
Perhatikan beberapa variasi untuk mendeklarasikan dan menggunakan thread pada contoh berikut ini.

 






Jika kita menggunakan Runnable, pada dasarnya kita menyatakan bahwa kita ingin membuat suatu proses -- yang implementasinya berada di dalam metode run() -- bukan suatu objek yang melakukan proses tertentu. Tentunya hal ini tergantung dari cara pandang kita, apakah kita ingin menganggap suatu thread sebagai objek atau sesuatu yang sama sekali berbeda, yaitu proses.
Jika kita menganggap suatu thread sebagai proses, tetntunya kita akan terbebas dari cara pandang berorientasi objek yaitu "semuanya adalah objek". Artinya juga, kita tidak perlu membuat seluruh kelas menjadi Runnable jika hanya kita ingin memulai proses di bagian tertentu program kita. Karenanya, mungkin lebih masuk akal untuk menyembunyikan thread di dalam kelas kita menggunakan kelas dalam.
KelasDalamBernama[.code] membuat kelas dalam yang merupakan kelas turunan dari kelas Thread, dan membuat instansi kelas ini di dalam konstruktornya. Cara ini baik jika kita ingin kelas dalam tersebut memiliki suatu kemampuan tertentu (metode lain) yang ingin kita gunakan. Akan tetapi, seringkali kita membuat thread hanya untuk memanfaatkan [code]Thread saja, artinya kita mungkin tidak perlu membuat kelas yang memiliki nama.
KelasDalamAnonim adalah alternatif dari KelasDalamBernama di mana kelas dalamnya merupakan kelas anonim yang merupakan kelas turunan dari kelas Thread. Kelas anonim ini dibuat di dalam konstruktor dan disimpan dalam bentuk referensi t bertipe Thread. Jika metode kelas lain membutuhkan akses ke t, maka kita bisa menggunakannya seperti Thread biasa tanpa perlu mengetahui tipe objek t sesungguhnya.
Kelas ketiga dan keempat pada contoh di atas mirip dengan contoh pertama dan kedua, akan tetapi menggunakan interface Runnable. Contoh ini hanya ingin menunjukkan bahwa menggunakan Runnable tidak menambah nilai apa-apa, kecuali membuat kodenya lebih sulit dibaca.
Kelas ThreadViaMetode menunjukkan bagaimana membuat thread dari dalam metode. Kita bisa memanggil metode tersebut jika kita siap untuk menjalankan thread ini. Metode ini akan selesai setelah thread berjalan. Jika thread hanya melakukan tugas sampingan, mungkin cara ini lebih cocok daripada mengimplementasikan kelas khusus untuk melakukan fungsi-fungsi thread.










Tidak ada komentar:

Posting Komentar