Objek Thread menyimpan dua properti yang tampak seperti tuas kendali: prioritas dan status daemon. Keduanya memang berguna, tetapi keduanya juga sering disalahpahami pemula sebagai jaminan yang sebenarnya tidak pernah diberikan Java. Prioritas hanyalah petunjuk bagi penjadwal — bukan perintah — dan daemon hanyalah cara menandai thread sebagai pekerja latar belakang yang boleh ditinggalkan saat program keluar. Chapter ini menguji kedua properti tersebut dengan eksperimen nyata agar ekspektasi kita lurus sebelum masuk ke wilayah yang lebih berbahaya: race condition.

Prioritas Thread Hanyalah Petunjuk #

Setiap thread memiliki angka prioritas berkisar dari Thread.MIN_PRIORITY (1) hingga Thread.MAX_PRIORITY (10), dengan Thread.NORM_PRIORITY (5) sebagai nilai bawaan. Thread baru mewarisi prioritas dari thread yang menciptakannya, sehingga thread yang kita buat dari main otomatis bernilai 5. Method setPriority() mengubah angka tersebut dan getPriority() membacanya kembali.

public class PrioritasThread {
    public static void main(String[] args) throws InterruptedException {
        int[] hasil = new int[3];
        int[] tingkat = {Thread.MIN_PRIORITY, Thread.NORM_PRIORITY, Thread.MAX_PRIORITY};
        String[] label = {"rendah", "normal", "tinggi"};

        System.out.println("prioritas thread main: " + Thread.currentThread().getPriority());

        Thread[] pekerja = new Thread[3];
        for (int i = 0; i < pekerja.length; i++) {
            int indeks = i;
            pekerja[i] = new Thread(() -> {
                int jumlah = 0;
                int awal = indeks * 1000 + 1;
                int akhir = (indeks + 1) * 1000;
                for (int n = awal; n <= akhir; n++) {
                    jumlah += n;
                }
                hasil[indeks] = jumlah;
            });
            pekerja[i].setName("pekerja-" + label[i]);
            pekerja[i].setPriority(tingkat[i]);
            pekerja[i].start();
        }

        for (Thread t : pekerja) {
            t.join();
        }

        for (int i = 0; i < pekerja.length; i++) {
            System.out.println(pekerja[i].getName() + " (prioritas "
                    + pekerja[i].getPriority() + ") menjumlahkan "
                    + (i * 1000 + 1) + "-" + ((i + 1) * 1000) + ": " + hasil[i]);
        }
    }
}

Output yang Dihasilkan:

text
prioritas thread main: 5
pekerja-rendah (prioritas 1) menjumlahkan 1-1000: 500500
pekerja-normal (prioritas 5) menjumlahkan 1001-2000: 1500500
pekerja-tinggi (prioritas 10) menjumlahkan 2001-3000: 2500500

Mari kita telaah baris-baris kode di atas:

  1. Baris 8 (System.out.println("prioritas thread main: ...")): membuktikan nilai bawaan main adalah 5 (NORM_PRIORITY); seluruh thread pekerja mewarisinya sebelum kita mengubahnya.
  2. Baris 11-24 (loop pembuatan thread): tiga thread dibuat dengan prioritas 1, 5, dan 10. Masing-masing menjumlahkan rentang bilangan berbeda dan menulis ke slot array tetap — pola kumpulkan-lalu-cetak yang sudah kita kenal.
  3. Baris 26-28 (t.join()): main menunggu ketiganya tanpa peduli siapa selesai lebih dulu.
  4. Baris 30-35 (pencetakan hasil): hasil dicetak terurut berdasarkan indeks, bukan berdasarkan urutan penyelesaian. Prioritas tidak mengubah kebenaran hasil — ketiga rentang terjumlahkan penuh apa pun urutan eksekusinya.

Thread Daemon untuk Pekerjaan Latar Belakang #

Aturan hidup-mati JVM sederhana: JVM terus berjalan selama masih ada thread biasa (user thread) yang hidup; JVM keluar begitu yang tersisa hanya thread daemon. Status daemon cocok untuk pekerjaan latar belakang yang sifatnya membantu — pemantauan berkala, pengumpulan metrik, heartbeat — yang tidak bermakna lagi ketika program utama selesai. JVM sendiri memakai daemon untuk garbage collector dan pekerjaan internalnya.

Status daemon diatur dengan setDaemon(true) dan hanya boleh dipanggil sebelum start(); memanggilnya pada thread yang sudah hidup melempar IllegalThreadStateException. Program berikut membandingkan dua thread secara berdampingan — yang satu daemon, yang lain biasa:

public class ThreadDaemon {
    public static void main(String[] args) throws InterruptedException {
        // Bagian 1: thread daemon -- tidak menahan JVM tetap hidup.
        Thread daemon = new Thread(() -> {
            try {
                Thread.sleep(500);
                // Baris di bawah ini TIDAK PERNAH tercetak: JVM sudah keluar
                // lebih dulu karena hanya thread daemon yang tersisa.
                System.out.println("baris ini tidak pernah tercetak");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "pemantau-daemon");
        daemon.setDaemon(true); // wajib dipanggil sebelum start()
        daemon.start();

        System.out.println("daemon aktif : " + daemon.isDaemon());

        // Bagian 2: thread biasa (user thread) -- menahan JVM tetap hidup.
        Thread user = new Thread(() -> {
            try {
                Thread.sleep(200);
                System.out.println("pemantau-user selesai -- JVM menunggu thread ini");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "pemantau-user");
        user.start();

        System.out.println("main selesai -- sisanya diserahkan pada JVM");
    }
}

Output yang Dihasilkan:

text
daemon aktif : true
main selesai -- sisanya diserahkan pada JVM
pemantau-user selesai -- JVM menunggu thread ini

Mari kita telaah kronologi program tersebut:

  1. Baris 5-15 (thread daemon): pemantau-daemon tidur 500 milidetik, lebih lama dari usia keseluruhan program. Baris cetak di dalam body-nya sengaja ada di source code, tetapi tidak pernah muncul di keluaran — bukti bahwa JVM keluar tanpa menunggu daemon selesai.
  2. Baris 17 (daemon.setDaemon(true)): penandaan harus terjadi sebelum start(); setelah thread hidup, identitas daemon tidak bisa lagi diubah.
  3. Baris 19 (System.out.println("daemon aktif : ...")): isDaemon() mengembalikan true, menegaskan penandaan berhasil.
  4. Baris 22-30 (thread biasa): pemantau-user tidur 200 milidetik lalu mencetak pesan. Thread main sudah selesai lebih dulu, tetapi JVM tetap hidup menunggu thread biasa ini — pesannya berhasil tercetak.
  5. Urutan keluaran: dua baris pertama dicetak oleh main pada awal eksekusi; baris terakhir dicetak pemantau-user setelah tidurnya usai. Setelah baris itu, hanya daemon yang tersisa — JVM langsung keluar dan eksekusi daemon yang tersisa dibatalkan.

Dua Properti yang Tidak Mengubah Siklus #

Baik prioritas maupun status daemon tidak menambah atau mengurangi state pada diagram Thread Lifecycle sebelumnya — thread daemon tetap berjalan melewati NEW, RUNNABLE, hingga TERMINATED seperti thread biasa, hanya saja JVM berhak meninggalkannya di tengah jalan. Prioritas pun tidak muncul di diagram karena hanya memengaruhi bagaimana penjadwal membagi waktu, bukan kemana state berpindah. Dengan semua kendali “lembut” ini di tangan, pertanyaan berikutnya justru makin tajam: apa yang terjadi bila beberapa thread mengubah data yang sama tanpa koordinasi? Jawabannya adalah topik Chapter Race Conditions.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

PrioritasThread.java

Unduh berkas
java
public class PrioritasThread {
    public static void main(String[] args) throws InterruptedException {
        int[] hasil = new int[3];
        int[] tingkat = {Thread.MIN_PRIORITY, Thread.NORM_PRIORITY, Thread.MAX_PRIORITY};
        String[] label = {"rendah", "normal", "tinggi"};

        System.out.println("prioritas thread main: " + Thread.currentThread().getPriority());

        Thread[] pekerja = new Thread[3];
        for (int i = 0; i < pekerja.length; i++) {
            int indeks = i;
            pekerja[i] = new Thread(() -> {
                int jumlah = 0;
                int awal = indeks * 1000 + 1;
                int akhir = (indeks + 1) * 1000;
                for (int n = awal; n <= akhir; n++) {
                    jumlah += n;
                }
                hasil[indeks] = jumlah;
            });
            pekerja[i].setName("pekerja-" + label[i]);
            pekerja[i].setPriority(tingkat[i]);
            pekerja[i].start();
        }

        for (Thread t : pekerja) {
            t.join();
        }

        for (int i = 0; i < pekerja.length; i++) {
            System.out.println(pekerja[i].getName() + " (prioritas "
                    + pekerja[i].getPriority() + ") menjumlahkan "
                    + (i * 1000 + 1) + "-" + ((i + 1) * 1000) + ": " + hasil[i]);
        }
    }
}

ThreadDaemon.java

Unduh berkas
java
public class ThreadDaemon {
    public static void main(String[] args) throws InterruptedException {
        // Bagian 1: thread daemon -- tidak menahan JVM tetap hidup.
        Thread daemon = new Thread(() -> {
            try {
                Thread.sleep(500);
                // Baris di bawah ini TIDAK PERNAH tercetak: JVM sudah keluar
                // lebih dulu karena hanya thread daemon yang tersisa.
                System.out.println("baris ini tidak pernah tercetak");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "pemantau-daemon");
        daemon.setDaemon(true); // wajib dipanggil sebelum start()
        daemon.start();

        System.out.println("daemon aktif : " + daemon.isDaemon());

        // Bagian 2: thread biasa (user thread) -- menahan JVM tetap hidup.
        Thread user = new Thread(() -> {
            try {
                Thread.sleep(200);
                System.out.println("pemantau-user selesai -- JVM menunggu thread ini");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "pemantau-user");
        user.start();

        System.out.println("main selesai -- sisanya diserahkan pada JVM");
    }
}