Di bawah ini adalah 37 contoh soal Pemrograman pertanyaan algoritma java beserta jawaban lengkap yang dapat di jadikan referensi. Baca yang lain Bagaimana Cara Membuat Pemrograman Java Yang Efisien?.
1. Mengapa menggunakan kerangka kerja Executor?
Ada beberapa keuntungan menggunakan framework Executor:
- Meningkatkan performa program: menggunakan thread pool untuk mengelola thread. Dapat mengurangi konsumsi yang disebabkan oleh pembuatan dan penghancuran thread.
- Menghindari kehabisan sumber daya: Kumpulan thread dapat membatasi jumlah thread yang bersamaan. Sehingga menghindari masalah kehabisan sumber daya.
- Menyediakan pemrograman asinkron: Kerangka kerja Executor menyediakan dukungan untuk pemrograman asinkron. Memungkinkan pengembang untuk melakukan beberapa tugas secara bersamaan dalam program. Sehingga meningkatkan daya tanggap dan keluaran program.
- Menyediakan antrian tugas: Kerangka kerja Executor menyediakan dukungan untuk antrian tugas. Anda dapat memasukkan beberapa tugas sesuai dengan aturan tertentu ke dalam antrian tugas. Kemudian dengan kumpulan thread sesuai dengan strategi tertentu untuk mengambil tugas dan eksekusi.
- Menyediakan antarmuka terpadu: Kerangka kerja Executor menyediakan serangkaian antarmuka yang memungkinkan pengembang untuk dengan mudah mengelola kumpulan thread dan antrian tugas. Sehingga mengurangi kompleksitas kode.
Sebaliknya, membuat sebuah Thread() baru memiliki beberapa kelemahan:
- Konsumsi Performa: Membuat thread memakan waktu dan sumber daya.
- Kurangnya manajemen: Thread yang dibuat dengan memanggil Thread() baru tidak dikelola dan disebut thread liar, dan dapat dibuat tanpa batas waktu. Persaingan antar thread dapat menyebabkan penggunaan sumber daya sistem yang berlebihan dan menyebabkan kelumpuhan sistem. Serta pergantian yang sering terjadi di antara thread, yang juga menghabiskan banyak sumber daya sistem.
- Tidak nyaman untuk ekspansi: thread yang dimulai menggunakan Thread () baru tidak kondusif untuk ekspansi. Seperti eksekusi berjangka waktu, eksekusi berkala, eksekusi berkala berjangka waktu, interupsi thread, dll. Tidak nyaman untuk diimplementasikan.
- Oleh karena itu, menggunakan kerangka kerja Executor adalah pilihan yang lebih baik. Dapat membuat pemrograman konkuren menjadi lebih mudah dan efisien.
2. Apa yang dimaksud dengan thread pool? Mengapa saya harus menggunakannya?
thread pool adalah teknik untuk mengelola banyak benang. Teknik ini membuat sejumlah thread pada awal program dan menempatkannya dalam sebuah wadah. Ketika sebuah tugas tiba, sebuah thread yang menganggur dikeluarkan dari wadah untuk melakukan tugas tersebut.
Ketika tugas tersebut selesai, thread tersebut dimasukkan kembali ke dalam wadah untuk menunggu tugas berikutnya. Hal ini menghindari seringnya pembuatan dan penghancuran thread dan meningkatkan kinerja dan stabilitas program.
Keuntungan utama menggunakan thread pool adalah:
- Mengurangi konsumsi sumber daya. Dengan menggunakan kembali thread yang telah dibuat, overhead yang disebabkan oleh pembuatan dan penghancuran thread akan berkurang.
- Peningkatan daya tanggap. Ketika sebuah tugas tiba, tugas tersebut dapat segera dieksekusi tanpa menunggu pembuatan thread.
- Meningkatkan pengelolaan thread. Thread pool dapat mengalokasikan, menyetel, dan memantau status thread secara seragam. Sehingga menghindari persaingan sumber daya dan masalah kelebihan memori yang disebabkan oleh terlalu banyak thread.
Kekurangan utama dari penggunaan thread pool adalah:
- Thread pool tidak dapat digunakan untuk tugas dengan siklus hidup yang panjang atau besar. Karena ini akan menghabiskan sumber daya dalam thread pool dan memengaruhi eksekusi tugas lain.
- Thread pool tidak dapat menetapkan prioritas pada tugas, dan juga tidak dapat mengidentifikasi status individual dari sebuah thread, seperti dimulai, dihentikan, dll.
- Thread pool hanya dapat memiliki satu untuk setiap domain aplikasi. Jika Anda ingin meletakkan thread ke dalam domain aplikasi yang berbeda, Anda perlu membuat thread pool baru.
- Thread pool semua thread berada dalam unit multi-threaded. Jika Anda ingin meletakkan thread ke dalam unit single-threaded, Anda perlu mengimplementasikannya sendiri.
- Dari JDK1.5 dan seterusnya, Java menyediakan kerangka kerja Executor untuk mendukung berbagai jenis thread pool. Seperti ukuran tetap, dapat di-cache, berjangka waktu, contoh tunggal, dll. Anda dapat memilih thread pool yang sesuai untuk melakukan tugas sesuai dengan skenario yang berbeda.
3. Perbedaan antara Executor, ExecutorService, dan Executors di Java?
Di Java, Executor, ExecutorService, dan Eksekutor merupakan komponen penting yang digunakan untuk mengelola dan mengontrol thread.
Executor adalah antarmuka yang mendefinisikan metode: execute (Runnable) yang digunakan untuk melakukan tugas-tugas berulir. Ini adalah dasar dari kerangka kerja eksekusi thread di Java, yang memungkinkan pembuatan dan pengelolaan kumpulan thread.
ExecutorService adalah sub-antarmuka dari Executor yang menyediakan metode tambahan untuk eksekusi tugas dan manajemen kumpulan thread. Ini termasuk:
- Submit(Runnable) dan submit(Callable): kedua metode tersebut dapat digunakan untuk mengeksekusi task. Di mana task yang dapat dipanggil memiliki nilai balik setelah eksekusi. Kedua metode tersebut mengembalikan objek Future, yang dapat digunakan untuk mendapatkan hasil eksekusi tugas atau membatalkan tugas.
- invokeAll(Collection) dan invokeAny(Collection): metode-metode ini dapat mengeksekusi sekumpulan tugas yang dapat dipanggil secara bersamaan dan mengembalikan daftar semua Future atau hasil dari salah satu tugas.
- shutdown() dan shutdownNow(): Metode-metode ini digunakan untuk mematikan kumpulan thread, dengan metode shutdownNow() yang mencoba untuk membatalkan tugas yang sedang dieksekusi.
- Executors adalah kelas alat yang menyediakan sekumpulan metode pabrik statis untuk membuat berbagai jenis thread pool, metode-metode ini meliputi:
- newSingleThreadExecutor(): membuat thread pool dengan hanya satu thread.
- newFixedThreadPool(int nThreads): membuat kumpulan thread dengan ukuran tetap.
- newCachedThreadPool(): membuat kumpulan thread yang dapat membuat thread baru atau menggunakan kembali thread yang menganggur sesuai kebutuhan.
- newScheduledThreadPool(int corePoolSize): membuat thread pool yang mendukung eksekusi tugas terjadwal dan berkala.
Singkatnya, Executor adalah antarmuka yang mendefinisikan metode dasar eksekusi tugas berulir. ExecutorService adalah perpanjangan dari antarmuka Executor yang menyediakan lebih banyak eksekusi tugas dan kemampuan manajemen thread pool. Dan Executors adalah kelas alat yang menyediakan serangkaian metode untuk membuat berbagai jenis thread pool.
4. Apa yang dimaksud dengan operasi atomic ? Apa saja atomic class dalam API Java?
Operasi atom adalah operasi atau serangkaian operasi yang tidak terputus yang memastikan konsistensi dan keamanan data dalam lingkungan multi-threaded. Terlebih operasi atom dapat diimplementasikan melalui penguncian cache prosesor atau penguncian bus. Atau melalui penguncian Java dan pendekatan CAS (Compare And Set) siklik.
Pada Java Concurrency API, terdapat sejumlah kelas atom (atomic class) yang dapat memberikan dukungan untuk operasi atom. Kelas-kelas tersebut berada pada paket java.util.concurrent.atomic. Beberapa kelas-kelas atomik ini terdiri dari beberapa tipe berikut:
- Tipe dasar pembaruan atomik: AtomicBoolean, AtomicInteger, AtomicLong, AtomicReference
- Larik pembaruan atom: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray
- Properti pembaruan atom: AtomicLongFieldUpdater, AtomicIntegerFieldUpdater, AtomicReferenceFieldUpdater
- Kelas-kelas atomik untuk menyelesaikan masalah ABA: AtomicMarkableReference, AtomicStampedReference
Kelas-kelas atom ini menyediakan beberapa metode umum, seperti get(), set(), getAndSet(), incrementAndGet(), getAndIncrement(), dll. Menggunakan kelas-kelas atomik ini akan menyederhanakan kode, meningkatkan performa, dan memastikan keamanan thread.
5. Perbedaan antara thread dan proses Java?
Proses adalah unit terkecil dari sistem operasi yang mengalokasikan sumber daya dan thread adalah unit terkecil dari sistem operasi yang dijadwalkan.
Sebuah program memiliki setidaknya satu proses dan sebuah proses memiliki setidaknya satu thread.
Thread berbagi ruang alamat dan sumber daya proses, sementara proses tidak bergantung satu sama lain.
Thread dibuat dan dialihkan lebih cepat dan dengan overhead yang lebih sedikit daripada proses.
Komunikasi dan sinkronisasi antar thread lebih mudah daripada antar proses.
Multi-threading meningkatkan konkurensi program, tetapi juga meningkatkan kompleksitas dan risiko.
Thread dibatasi oleh global interpreter locks (GIL) dan tidak dapat sepenuhnya menggunakan CPU multi-core, sedangkan multi-proses bisa.
6. Ada berapa banyak cara di Java untuk mengimplementasikan sebuah thread?
Ada empat cara untuk mengimplementasikan sebuah thread di java, yaitu:
Mewarisi kelas Thread dan menimpa metode run (). Contoh:
class PrimeThread extends Thread { long minPrime; PrimeThread (long minPrime) { this.minPrime = minPrime; } public void run () {
// compute primes larger than minPrime . . . }}
// Membuat dan memulai thread
PrimeThread p = new PrimeThread (143);p.start ();
mengimplementasikan antarmuka Runnable dan mengimplementasikan metode run (). Contoh:
class PrimeRun implements Runnable { long minPrime; PrimeRun (long minPrime) { this.minPrime = minPrime; } public void run () { // compute primes larger than minPrime . . . }}
// Membuat dan memulai thread
PrimeRun p = new PrimeRun (143);new Thread (p).start ();
Gunakan inner class anonim, yaitu mengoper instance Runnable secara langsung ketika membuat objek Thread. Contoh:
new Thread(new Runnable() { public void run() { // do something . . . }}).start();
Mengimplementasikan antarmuka Callable dan mengimplementasikan metode call (). Metode ini memungkinkan thread mengembalikan hasil, dan harus digunakan bersama dengan FutureTask. Sebagai contoh:
class PrimeCall implements Callable<Integer> { long minPrime; PrimeCall (long minPrime) { this.minPrime = minPrime; } public Integer call() { // compute and return the number of primes larger than minPrime . . . }}
// Membuat dan memulai thread PrimeCall p = new PrimeCall (143);FutureTask<Integer> task = new FutureTask<>(p);new Thread(task).start();
// Mendapatkan hasil pengembalian dari Thread
Integer result = task.get();
Perbedaan antara mewarisi kelas Thread dan mengimplementasikan antarmuka Runnable adalah sebuah kelas tidak dapat mewarisi dari kelas Thread. Jika sudah mewarisi dari kelas lain, tetapi kelas tersebut dapat mengimplementasikan antarmuka Runnable. Perbedaan antara mengimplementasikan antarmuka Callable adalah bahwa antarmuka ini memungkinkan thread memiliki nilai balik dan melempar pengecualian.
7. Bagaimana cara berbagi data di antara dua thread?
Ada beberapa cara umum untuk berbagi data di antara dua thread:
- Mengabstraksikan data ke dalam sebuah kelas dan mengenkapsulasi operasi pada data ini di dalam metode kelas. Pendekatan ini hanya membutuhkan penambahan kata kunci yang disinkronkan ke metode untuk menyinkronkan data. Sebagai contoh, sistem tiket dapat diimplementasikan dengan cara ini.
- Gunakan objek Runnable sebagai kelas dalam dari sebuah kelas dan data yang dibagikan sebagai variabel anggota kelas. Pendekatan ini memungkinkan objek Runnable yang berbeda untuk memanggil metode yang memanipulasi data bersama dalam kelas yang sama. Sebagai contoh, sistem transfer bank dapat diimplementasikan dengan cara ini.
- Gunakan mekanisme komunikasi antar thread seperti wait/notify atau BlockingQueue, yang memungkinkan satu thread untuk berbagi data sambil menunggu hasil operasi thread lain. Sebagai contoh, masalah produsen-konsumen dapat diimplementasikan dengan cara ini.
Secara umum, variabel yang digunakan bersama mengharuskan variabel itu sendiri menjadi thread-safe, dan ketika digunakan di dalam sebuah thread. Jika ada operasi komposit pada variabel yang digunakan bersama, maka operasi komposit tersebut juga harus dijamin atomic.
Baca juga Belajar Pemrograman Java Untuk Pemula Paling Efektif
8. Apa yang dimaksud dengan antrian pemblokiran?
Apa prinsip di balik penerapan antrean pemblokiran? Bagaimana cara menggunakan antrian pemblokiran untuk mengimplementasikan model produsen-konsumen?
- BlockingQueue (BlockingQueue) adalah dukungan untuk antrian yang aman untuk thread. Dapat memblokir thread produsen atau thread konsumen hingga antrian memenuhi persyaratan.
- Prinsip implementasi blocking queue didasarkan pada kunci dan variabel kondisi. Ketika antrian kosong, thread konsumen akan diblokir pada metode take() sampai sebuah elemen tersedia dalam antrian. Ketika antrian penuh, thread produsen akan diblok pada metode put() dan tidak akan dibangunkan sampai ada ruang yang tersedia dalam antrian.
- Memblokir antrian dapat digunakan untuk mengimplementasikan model produsen-konsumen. Thread produsen memasukkan data ke dalam antrian dan thread konsumen mengambil data dari antrian. Thread produsen dan thread konsumen dapat berjalan secara independen tanpa harus mengkhawatirkan apakah antrian kosong atau penuh.
Berikut ini adalah contoh bagaimana mengimplementasikan model produsen-konsumen dengan menggunakan blocking queue:
// Thread produsen
public class Producer implements Runnable {
private BlockingQueue<String> queue;
public Producer(BlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
queue.put("data-" + i);
System.out.println("producer put data-" + i); Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}}
// Thread konsumen
public class Consumer implements Runnable {
private BlockingQueue<String> queue;
public Consumer(BlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
while (true) {
String data = queue.take();
System.out.println("consumer get data-" + data); Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}}
Kode di atas membuat dua thread, yaitu thread produsen dan thread konsumen. Thread produsen memasukkan data ke dalam antrean dan thread konsumen mengambil data dari antrean. Thread produsen dan thread konsumen dapat berjalan secara independen tanpa mengkhawatirkan apakah antrean kosong atau penuh.
Dengan menjalankan kode di atas, Anda dapat melihat bahwa thread produsen memasukkan data ke dalam antrian setiap 1 detik dan thread konsumen mengambil data dari antrian setiap 1 detik.
9. Apa yang dimaksud dengan Callable dan Future Java?
Antarmuka Callable mirip dengan antarmuka Runnable karena keduanya digunakan untuk mengenkapsulasi tugas yang akan dijalankan di thread lain. Tetapi antarmuka Callable memiliki beberapa perbedaan:
Antarmuka Callable adalah antarmuka generik yang memungkinkan Anda untuk menentukan jenis nilai kembalian, sedangkan metode jalankan antarmuka Runnable tidak memiliki nilai kembalian (void).
Metode pemanggilan antarmuka Callable dapat melemparkan pengecualian, sedangkan metode run antarmuka Runnable tidak bisa.
Tugas antarmuka Callable mengembalikan objek Future setelah dieksekusi, yang mewakili hasil komputasi asinkron, sedangkan antarmuka Runnable tidak.
Antarmuka Future merepresentasikan hasil komputasi asinkron dan menyediakan metode untuk memeriksa apakah komputasi sudah selesai. Menunggu komputasi selesai, dan mendapatkan hasil komputasi Antarmuka Future memiliki metode utama berikut:
- boolean cancel(boolean mayInterruptIfRunning): membatalkan eksekusi tugas, parameternya menunjukkan apakah diizinkan untuk menginterupsi tugas yang sedang berjalan tetapi belum selesai.
- boolean isCancelled(): menentukan apakah tugas berhasil dibatalkan.
- boolean isDone(): menilai apakah tugas telah selesai.
- V get() melempar InterruptedException, ExecutionException: mendapatkan hasil dari tugas, jika tugas tidak selesai maka akan memblokir dan menunggu.
- V get(long timeout, unit TimeUnit) melempar InterruptedException, ExecutionException,
- TimeoutException: mendapatkan hasil tugas, jika tugas tidak selesai dalam waktu yang ditentukan maka pengecualian waktu habis akan dilemparkan.
Singkatnya, Anda dapat menganggap Callable dan Future adalah sepasang kombinasi. Callable digunakan untuk menghasilkan hasil, Future digunakan untuk mendapatkan hasil.
Contoh kode:
// Membuat kelas implementasi antarmuka Callable yang mengganti metode pemanggilan untuk mengembalikan sebuah string
class MyCallable implements Callable<String> { public String call() throws Exception {
// Mensimulasikan operasi yang memakan waktu
Thread.sleep(3000); return "Hello Callable"; }}
// Dalam metode utama, buat objek thread pool
ExecutorService executor = Executors.newFixedThreadPool(2);
// Membuat objek MyCallable
MyCallable task = new MyCallable();
// Gunakan metode submit pada thread pool untuk mengirimkan tugas dan menerima objek Future yang dikembalikan
Future<String> future = executor.submit(task);
// Lakukan sesuatu yang lain
System.out.println("Thread utama sedang dieksekusi...");
// Panggil metode get dari Future untuk mendapatkan hasil; jika tugas belum selesai, tugas akan diblokir dan menunggu.
String result = future.get();
// Hasil keluaran
System.out.println("Hasil dari tugas tersebut adalah:" + result);
// Tutup thread pool
executor.shutdown();
10. Apa Itu FutureTask?
FutureTask adalah kelas yang mengimplementasikan antarmuka RunnableFuture, yang mewarisi antarmuka Runnable dan Future. Dengan demikian, FutureTask dapat dikirimkan ke thread pool untuk dieksekusi sebagai tugas atau dikembalikan ke pemanggil sebagai hasil komputasi asinkron.
Konstruktor FutureTask dapat menerima sebuah Callable atau Runnable dan objek hasil sebagai argumen. Jika sebuah Callable dioper, maka metode menjalankan FutureTask akan memanggil metode pemanggilan Callable dan menetapkan nilai balik ke hasil dari FutureTask.
Jika sebuah Runnable dan objek hasil dioper, maka metode menjalankan FutureTask akan memanggil metode menjalankan Runnable dan menetapkan objek hasil ke hasil dan menetapkan objek hasil ke hasil dari FutureTask.
FutureTask dapat digunakan untuk mengimplementasikan beberapa skenario konkurensi yang efisien seperti:
- Pra-pemuatan data: Jika ada operasi pemuatan data yang memakan waktu, Anda dapat membuat FutureTask untuk melakukan operasi ini di awal program. Lalu memanggil metode get dari FutureTask untuk mendapatkan hasil saat Anda perlu menggunakan data. Sehingga Anda dapat menggunakan waktu luang untuk memuat data terlebih dahulu untuk meningkatkan kecepatan respons program.
- Cache hasil kalkulasi: Jika ada operasi kalkulasi yang kompleks, dan mungkin dipanggil beberapa kali. Anda dapat menggunakan ConcurrentHashMap untuk menyimpan hasil kalkulasi. Dengan kunci sebagai parameter input dan nilai sebagai objek FutureTask. Ketika ada parameter input baru, pertama-tama periksa apakah ada FutureTask yang sesuai di cache. Jika tidak, buat FutureTask baru dan masukkan ke dalam cache. Lalu jalankan FutureTask ini dan kembalikan hasilnya; jika ada, langsung kembalikan hasil FutureTask ini. Hal ini dapat menghindari penghitungan berulang dan meningkatkan efisiensi program.
Contoh kode
// Membuat implementasi antarmuka Callable yang mengganti metode pemanggilan dan mengembalikan sebuah string.
class MyCallable implements Callable<String> { public String call() throws Exception {
// Mensimulasikan operasi yang memakan waktu
Thread.sleep(3000); return "Hello Callable"; }}
// Dalam metode utama, buat objek FutureTask, dengan mengoper objek MyCallable sebagai parameter
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
// Membuat objek thread, dengan memasukkan objek FutureTask sebagai parameter
Thread thread = new Thread(futureTask);
// Memulai thread
thread.start();
// Melakukan beberapa hal lain
System.out.println("thread utama sedang dieksekusi...");
// Panggil metode get dari FutureTask untuk mendapatkan hasil, dan jika tugas belum selesai, maka akan diblok dan menunggu
String result = futureTask.get();
// Keluarkan hasilnya
System.out.println("Hasil dari misi tersebut adalah:" + result);