Pertanyaan wawancara multithreading Java
Proses adalah lingkungan berjalan mandiri, yang dapat dianggap sebagai program atau aplikasi. Thread adalah tugas yang dieksekusi dalam suatu proses. Lingkungan runtime Java adalah proses tunggal yang berisi kelas dan program berbeda. Utas bisa disebut proses ringan. Thread memerlukan lebih sedikit sumber daya untuk membuat dan berada dalam suatu proses, dan dapat berbagi sumber daya dalam proses tersebut.
Dalam program multi-thread, beberapa thread dijalankan secara bersamaan untuk meningkatkan efisiensi program. CPU tidak akan memasuki keadaan idle karena thread harus menunggu sumber daya. Beberapa thread berbagi memori heap, jadi lebih baik membuat beberapa thread untuk melakukan beberapa tugas daripada membuat banyak proses. Misalnya, Servlet lebih baik daripada CGI karena Servlet mendukung multi-threading sedangkan CGI tidak.
Saat kita membuat thread di program Java, itu disebut thread pengguna. Utas daemon adalah utas yang dijalankan di latar belakang dan tidak mencegah penghentian JVM. Ketika tidak ada thread pengguna yang berjalan, JVM menutup program dan keluar. Thread anak yang dibuat oleh thread daemon masih merupakan thread daemon.
Ada dua cara untuk membuat thread: yang pertama adalah dengan mengimplementasikan antarmuka Runnable, lalu meneruskannya ke konstruktor Thread untuk membuat objek Thread; yang lainnya adalah dengan mewarisi kelas Thread secara langsung. Jika Anda ingin tahu lebih banyak, Anda dapat membaca artikel tentang cara membuat thread di Java.
Saat kita membuat thread baru di program Java, statusnya adalah Baru. Saat kita memanggil metode start() thread, statusnya diubah menjadi Runnable. Penjadwal thread mengalokasikan waktu CPU ke thread di kumpulan thread Runnable dan mengubah statusnya menjadi Running. Status thread lainnya termasuk Menunggu, Diblokir, dan Mati. Baca artikel ini untuk mempelajari lebih lanjut tentang siklus hidup thread.
Tentu saja, tetapi jika kita memanggil metode run() Thread, ia akan berperilaku seperti metode normal. Untuk mengeksekusi kode kita di thread baru, kita harus menggunakan metode Thread.start().
Kita bisa menggunakan metode Sleep() dari kelas Thread untuk menjeda thread selama jangka waktu tertentu. Perlu dicatat bahwa ini tidak menghentikan thread. Setelah thread dibangunkan dari tidur, status thread akan diubah menjadi Runnable, dan akan dieksekusi sesuai dengan jadwal thread.
Setiap thread memiliki prioritas. Secara umum, thread dengan prioritas tinggi akan memiliki prioritas saat dijalankan, tetapi hal ini bergantung pada penerapan penjadwalan thread, yang bergantung pada OS. Kita dapat menentukan prioritas thread, namun hal ini tidak menjamin bahwa thread berprioritas tinggi akan dieksekusi sebelum thread berprioritas rendah. Prioritas thread adalah variabel int (dari 1-10), 1 mewakili prioritas terendah, 10 mewakili prioritas tertinggi.
Penjadwal thread adalah layanan sistem operasi yang bertanggung jawab untuk mengalokasikan waktu CPU ke thread dalam status Runnable. Setelah kita membuat thread dan memulainya, eksekusinya bergantung pada implementasi penjadwal thread. Pemotongan waktu mengacu pada proses mengalokasikan waktu CPU yang tersedia ke thread Runnable yang tersedia. Mengalokasikan waktu CPU dapat didasarkan pada prioritas thread atau waktu tunggu thread. Penjadwalan thread tidak dikontrol oleh mesin virtual Java, jadi sebaiknya aplikasi yang mengontrolnya (artinya, jangan membuat program Anda bergantung pada prioritas thread).
Peralihan konteks adalah proses menyimpan dan memulihkan status CPU, yang memungkinkan eksekusi thread melanjutkan eksekusi dari titik interupsi. Peralihan konteks adalah fitur penting dari sistem operasi multitasking dan lingkungan multithread.
Kita dapat menggunakan metode joint() dari kelas Thread untuk memastikan bahwa semua thread yang dibuat oleh program berakhir sebelum metode main() keluar. Berikut adalah artikel tentang metode joint() kelas Thread.
Ketika sumber daya dapat dibagi antar thread, komunikasi antar thread merupakan sarana penting untuk mengoordinasikannya. Metode wait()/notify()/notifyAll() di kelas Object dapat digunakan untuk berkomunikasi antar thread tentang status kunci sumber daya. Klik di sini untuk mempelajari lebih lanjut tentang thread wait, notify, dan notifyAll.
Setiap objek di Java memiliki kunci (monitor, yang juga bisa menjadi monitor), dan metode seperti wait() dan notify() digunakan untuk menunggu objek dikunci atau memberi tahu thread lain bahwa monitor objek tersedia. Tidak ada kunci atau sinkronisasi yang tersedia untuk objek apa pun di thread Java. Itu sebabnya metode ini merupakan bagian dari kelas Object sehingga setiap kelas di Java memiliki metode dasar untuk komunikasi antar thread
Ketika sebuah thread perlu memanggil metode wait() suatu objek, thread tersebut harus memiliki kunci objek tersebut. Kemudian thread tersebut akan melepaskan kunci objek tersebut dan memasuki status menunggu hingga thread lain memanggil metode notify() pada objek tersebut. Demikian pula, ketika sebuah thread perlu memanggil metode notify() objek, maka thread tersebut akan melepaskan kunci objek sehingga thread lain yang menunggu dapat memperoleh kunci objek tersebut. Karena semua metode ini memerlukan thread untuk menahan kunci objek, yang hanya dapat dicapai melalui sinkronisasi, metode tersebut hanya dapat dipanggil dalam metode tersinkronisasi atau blok tersinkronisasi.
Metode sleep() dan yield() dari kelas Thread akan berjalan pada thread yang sedang dijalankan. Jadi tidak masuk akal untuk memanggil metode ini di thread lain yang menunggu. Itu sebabnya metode ini bersifat statis. Mereka dapat bekerja di thread yang sedang dijalankan dan mencegah pemrogram salah mengira bahwa metode ini dapat dipanggil di thread lain yang tidak berjalan.
Ada banyak cara untuk memastikan keamanan thread di Java - sinkronisasi, menggunakan kelas serentak atom, menerapkan kunci serentak, menggunakan kata kunci volatil, menggunakan kelas yang tidak dapat diubah, dan kelas aman thread. Anda dapat mempelajari lebih lanjut di tutorial keamanan thread.
Saat kita menggunakan kata kunci volatil untuk mengubah variabel, thread akan membaca variabel secara langsung dan tidak menyimpannya dalam cache. Hal ini memastikan bahwa variabel yang dibaca oleh thread konsisten dengan variabel yang ada di memori.
Blok yang disinkronkan adalah pilihan yang lebih baik karena tidak mengunci seluruh objek (tentu saja Anda juga dapat mengunci seluruh objek). Metode tersinkronisasi mengunci seluruh objek, meskipun ada beberapa blok tersinkronisasi yang tidak terkait di kelas, yang biasanya menyebabkan metode tersebut berhenti dijalankan dan harus menunggu untuk mendapatkan kunci pada objek.
Thread dapat diatur sebagai thread daemon menggunakan metode setDaemon(true) dari kelas Thread. Perlu dicatat bahwa metode ini perlu dipanggil sebelum memanggil metode start(), jika tidak maka IllegalThreadStateException akan dilempar.
ThreadLocal digunakan untuk membuat variabel lokal thread. Kita tahu bahwa semua thread suatu objek akan berbagi variabel globalnya, jadi variabel ini tidak aman untuk thread. Namun bila kita tidak ingin menggunakan sinkronisasi, kita dapat memilih variabel ThreadLocal.
Setiap thread akan memiliki variabel Threadnya sendiri, dan mereka dapat menggunakan metode get()/set() untuk mendapatkan nilai defaultnya atau mengubah nilainya di dalam thread. Instance ThreadLocal biasanya ingin status thread terkaitnya menjadi properti statis pribadi. Dalam contoh artikel ThreadLocal Anda dapat melihat program kecil tentang ThreadLocal.
ThreadGroup adalah kelas yang tujuannya adalah untuk memberikan informasi tentang grup thread.
API ThreadGroup relatif lemah dan tidak menyediakan fungsi lebih dari Thread. Ini memiliki dua fungsi utama: satu adalah untuk mendapatkan daftar utas aktif dalam grup utas; yang lainnya adalah untuk mengatur penangan pengecualian yang tidak tertangkap (penanganan pengecualian ncaught) untuk utas tersebut. Namun, di Java 1.5, kelas Thread juga menambahkan metode setUncaughtExceptionHandler(UncaughtExceptionHandler eh), sehingga ThreadGroup sudah usang dan tidak disarankan untuk terus digunakan.
t1.setUncaughtExceptionHandler(new UncaughtExceptionHandler(){ @Overridepublic void uncaughtException(Thread t, Throwable e) {System.out.println("terjadi pengecualian:"+e.getMessage());} });
Thread dump adalah daftar thread aktif JVM, yang sangat berguna untuk menganalisis kemacetan dan kebuntuan sistem. Ada banyak cara untuk mendapatkan thread dump - menggunakan Profiler, perintah Kill -3, alat jstack, dll. Saya lebih suka alat jstack karena mudah digunakan dan dilengkapi dengan JDK. Karena ini adalah alat berbasis terminal, kita dapat menulis beberapa skrip untuk menghasilkan thread dump secara berkala untuk dianalisis. Baca dokumen ini untuk mempelajari lebih lanjut tentang menghasilkan thread dump.
Kebuntuan mengacu pada situasi di mana lebih dari dua thread diblokir selamanya. Situasi ini memerlukan setidaknya dua thread lagi dan lebih dari dua sumber daya.
Untuk menganalisis kebuntuan, kita perlu melihat thread dump aplikasi Java. Kita perlu mencari tahu thread mana saja yang berstatus BLOCKED dan resource apa saja yang ditunggu. Setiap sumber daya memiliki id unik, dengan menggunakan id ini kita dapat mengetahui thread mana yang sudah memiliki kunci objeknya.
Menghindari kunci bertumpuk, menggunakan kunci hanya jika diperlukan, dan menghindari menunggu tanpa batas adalah cara umum untuk menghindari kebuntuan. Baca artikel ini untuk mempelajari cara menganalisis kebuntuan.
java.util.Timer adalah kelas alat yang dapat digunakan untuk menjadwalkan thread agar dieksekusi pada waktu tertentu di masa mendatang. Kelas Timer dapat digunakan untuk menjadwalkan tugas satu kali atau tugas berkala.
java.util.TimerTask adalah kelas abstrak yang mengimplementasikan antarmuka Runnable. Kita perlu mewarisi kelas ini untuk membuat tugas terjadwal kita sendiri dan menggunakan Timer untuk menjadwalkan eksekusinya.
Berikut adalah contoh tentang Java Timer.
Kumpulan thread mengelola sekelompok thread pekerja dan juga menyertakan antrian untuk menempatkan tugas yang menunggu untuk dieksekusi.
java.util.concurrent.Executors menyediakan implementasi antarmuka java.util.concurrent.Executor untuk membuat kumpulan thread. Contoh Kumpulan Thread menunjukkan cara membuat dan menggunakan kumpulan thread, atau membaca contoh ScheduledThreadPoolExecutor untuk mempelajari cara membuat tugas berkala.
Pertanyaan wawancara konkurensi Java
Operasi atom mengacu pada unit tugas operasi yang tidak terpengaruh oleh operasi lain. Operasi atom adalah sarana yang diperlukan untuk menghindari inkonsistensi data dalam lingkungan multi-thread.
int++ bukan operasi atomik, jadi ketika satu thread membaca nilainya dan menambahkan 1, thread lain mungkin membaca nilai sebelumnya, yang akan menyebabkan kesalahan.
Untuk mengatasi masalah ini, kita harus memastikan bahwa operasi peningkatan bersifat atomik. Sebelum JDK1.5, kita dapat menggunakan teknologi sinkronisasi untuk melakukan ini. Pada JDK 1.5, paket java.util.concurrent.atomic menyediakan kelas pemuatan tipe int dan long yang secara otomatis menjamin bahwa operasinya bersifat atomik dan tidak memerlukan penggunaan sinkronisasi. Anda dapat membaca artikel ini untuk mempelajari tentang kelas atom Java.
Antarmuka Kunci menyediakan operasi kunci yang lebih terukur daripada metode tersinkronisasi dan blok tersinkronisasi. Mereka memungkinkan struktur yang lebih fleksibel yang dapat memiliki properti yang sangat berbeda, dan dapat mendukung beberapa kelas objek kondisional yang terkait.
Keuntungannya adalah:
Baca lebih lanjut tentang contoh kunci
Kerangka kerja Executor diperkenalkan di Java 5 dengan antarmuka java.util.concurrent.Executor. Kerangka kerja Pelaksana adalah kerangka kerja untuk tugas-tugas asinkron yang dipanggil, dijadwalkan, dijalankan, dan dikontrol berdasarkan serangkaian strategi eksekusi.
Pembuatan thread tanpa batas dapat menyebabkan memori aplikasi meluap. Jadi membuat kumpulan benang adalah solusi yang lebih baik karena jumlah benang dapat dibatasi dan benang tersebut dapat didaur ulang dan digunakan kembali. Sangat mudah untuk membuat kumpulan thread menggunakan kerangka Executors. Baca artikel ini untuk mempelajari cara membuat kumpulan thread menggunakan kerangka Executor.
Ciri-ciri java.util.concurrent.BlockingQueue adalah: ketika antrian kosong, operasi pengambilan atau penghapusan elemen dari antrian akan diblokir, atau ketika antrian penuh, operasi penambahan elemen ke antrian akan diblokir. .
Antrean pemblokiran tidak menerima nilai null. Saat Anda mencoba menambahkan nilai null ke antrean, NullPointerException akan muncul.
Implementasi antrian pemblokiran bersifat thread-safe, dan semua metode kueri bersifat atomik dan menggunakan kunci internal atau bentuk kontrol konkurensi lainnya.
Antarmuka BlockingQueue adalah bagian dari kerangka koleksi Java dan terutama digunakan untuk mengimplementasikan masalah produsen-konsumen.
Baca artikel ini untuk mempelajari cara menerapkan masalah produsen-konsumen menggunakan antrian pemblokiran.
Java 5 memperkenalkan antarmuka java.util.concurrent.Callable dalam paket konkurensi, yang sangat mirip dengan antarmuka Runnable, tetapi dapat mengembalikan objek atau memunculkan pengecualian.
Antarmuka Callable menggunakan obat generik untuk menentukan tipe pengembaliannya. Kelas Executors menyediakan beberapa metode berguna untuk menjalankan tugas dalam Callable di kumpulan thread. Karena tugas Callable bersifat paralel, kita harus menunggu hasilnya kembali. Objek java.util.concurrent.Future memecahkan masalah ini untuk kita. Setelah kumpulan thread mengirimkan tugas Callable, objek Future dikembalikan. Dengan menggunakannya, kita dapat mengetahui status tugas Callable dan mendapatkan hasil eksekusi yang dikembalikan oleh Callable. Future menyediakan metode get() sehingga kita bisa menunggu Callable berakhir dan mendapatkan hasil eksekusinya.
Baca artikel ini untuk mengetahui lebih banyak contoh tentang Callable, Future.
FutureTask adalah implementasi dasar Future, yang dapat kita gunakan dengan Executors untuk memproses tugas-tugas asinkron. Biasanya kita tidak perlu menggunakan kelas FutureTask, tapi ini menjadi sangat berguna ketika kita berencana untuk mengganti beberapa metode antarmuka Future dan mempertahankan implementasi dasar aslinya. Kita bisa mewarisinya dan mengganti metode yang kita perlukan. Baca contoh Java FutureTask untuk mempelajari cara menggunakannya.
Kelas koleksi Java bersifat cepat gagal, yang berarti ketika koleksi diubah dan thread menggunakan iterator untuk melintasi koleksi, metode next() iterator akan memunculkan pengecualian ConcurrentModificationException.
Kontainer serentak mendukung traversal serentak dan pembaruan serentak.
Kelas utamanya adalah ConcurrentHashMap, CopyOnWriteArrayList, dan CopyOnWriteArraySet. Baca artikel ini untuk mempelajari cara menghindari ConcurrentModificationException.
Executor menyediakan beberapa metode utilitas untuk kelas Executor, ExecutorService, ScheduledExecutorService, ThreadFactory, dan Callable.
Pelaksana dapat digunakan untuk membuat kumpulan thread dengan mudah.
Teks asli: journaldev.com Terjemahan: ifeve Penerjemah: Zheng Xudong