Thread bukan makhluk biner yang hanya “hidup” atau “sudah selesai” — JVM melacak perjalanan setiap thread melalui enam state yang terdefinisi rapi di enum Thread.State. Mengenal state-state tersebut bukan pelengkap teori: saat aplikasi tiba-tiba lambat atau berhenti merespons, dump thread menampilkan state setiap thread, dan kemampuan membacanya menentukan cepat atau lambatnya kita menemukan akar masalah. Chapter ini memetakan seluruh state beserta pemicu perpindahannya, lalu membuktikan lima state secara nyata lewat method getState().

Enam State Thread #

Enum Thread.State mendefinisikan enam nilai berikut:

  • NEW — objek Thread sudah dibuat, tetapi start() belum dipanggil. Objek hanyalah wadah biasa; thread sistem operasi belum ada.
  • RUNNABLE — thread sedang dieksekusi atau siap dieksekusi. Java menyatukan dua kondisi ini; detail apakah thread benar-benar mengisi core CPU atau hanya menunggu giliran adalah urusan penjadwal sistem operasi.
  • BLOCKED — thread menunggu intrinsic lock agar bisa masuk ke wilayah synchronized yang sedang dipegang thread lain.
  • WAITING — thread menunggu tanpa batas waktu, misalnya karena memanggil wait(), join() tanpa argumen, atau LockSupport.park(). Thread hanya bangkit bila ada aksi eksplisit dari thread lain: notify(), selesainya thread yang di-join(), atau unpark().
  • TIMED_WAITING — sama seperti WAITING, tetapi dengan batas waktu: sleep(millis), join(millis), atau wait(millis). Bila waktu habis, thread kembali RUNNABLE meski tanpa sinyal dari siapa pun.
  • TERMINATED — method run() sudah selesai (normal atau karena exception). Thread tidak bisa hidup kembali; objek Thread boleh dibaca state-nya, tetapi start() kedua melempar IllegalThreadStateException.

Peta Transisi State #

Seluruh perpindahan state dapat digambar menjadi satu diagram. Thread hanya bisa masuk ke jalur eksekusi lewat start(), dan hanya keluar dari RUNNABLE menuju TERMINATED ketika run() selesai:

Tiga state menunggu (BLOCKED, WAITING, TIMED_WAITING) terlihat serupa tetapi penyebabnya berbeda dan obatnya juga berbeda. Thread BLOCKED sembuh sendiri begitu pemegang lock melepaskannya; thread WAITING butuh thread lain yang bertindak; thread TIMED_WAITING diberi kesempatan bangkit sendiri ketika jatah waktunya habis.

Mengintip State dengan getState() #

Method getState() mengembalikan Thread.State saat pemanggilan berlangsung. Karena state bisa berubah kapan saja, pembacaan yang bermakna membutuhkan momen yang terkendali — itulah strategi program berikut: empat thread disusun dengan jeda waktu yang cukup lebar sehingga pada saat kita mengintip, posisi setiap thread pasti sudah stabil di state yang dituju:

java SiklusState.java
public class SiklusState {
    private static final Object kunci = new Object();

    public static void main(String[] args) throws InterruptedException {
        String[] catatan = new String[5];

        // sleeper: tidur 300 ms, selama itu state-nya TIMED_WAITING.
        Thread sleeper = new Thread(() -> {
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "sleeper");

        // worker: memegang lock "kunci" sambil tidur 400 ms.
        Thread worker = new Thread(() -> {
            synchronized (kunci) {
                try {
                    Thread.sleep(400);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }, "worker");

        // waiter: menunggu thread sleeper selesai lewat join tanpa batas waktu.
        Thread waiter = new Thread(() -> {
            try {
                sleeper.join();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "waiter");

        // contender: akan mengantre pada lock "kunci" yang sedang dipegang worker.
        Thread contender = new Thread(() -> {
            synchronized (kunci) {
                // masuk ke sini hanya setelah worker melepas kunci
            }
        }, "contender");

        catatan[0] = "sebelum start   : " + worker.getState(); // NEW

        sleeper.start();
        worker.start();
        Thread.sleep(50); // beri waktu worker mengambil kunci dan sleeper mulai tidur

        waiter.start();
        contender.start();
        Thread.sleep(100); // beri waktu contender mengantre dan waiter menunggu

        catatan[1] = "sedang tidur    : " + sleeper.getState();   // TIMED_WAITING
        catatan[2] = "menunggu join   : " + waiter.getState();    // WAITING
        catatan[3] = "mengantre kunci : " + contender.getState(); // BLOCKED

        sleeper.join();
        waiter.join();
        contender.join();
        worker.join();

        catatan[4] = "setelah selesai : " + worker.getState(); // TERMINATED

        for (String baris : catatan) {
            System.out.println(baris);
        }
    }
}

Output yang Dihasilkan:

text
sebelum start   : NEW
sedang tidur    : TIMED_WAITING
menunggu join   : WAITING
mengantre kunci : BLOCKED
setelah selesai : TERMINATED

Mari kita telaah baris-baris kode di atas:

  1. Baris 13-17 (Thread sleeper = ...): thread sleeper tidur 300 milidetik, sehingga berada di TIMED_WAITING selama periode tersebut.
  2. Baris 19-26 (Thread worker = ...): thread worker masuk blok synchronized(kunci) lalu tidur 400 milidetik — lock tetap dipegang selama tidur di dalam blok.
  3. Baris 39 (catatan[0] = ...): worker belum di-start(), jadi state-nya pasti NEW — momen ini satu-satunya kesempatan legal membaca NEW.
  4. Baris 42-43 (Thread.sleep(50)): jeda 50 milidetik memberi worker waktu mengambil lock sebelum contender dibuat; tanpa jeda ini, contender bisa lebih dulu memegang lock dan mengubah susunan drama.
  5. Baris 49-51 (catatan[1..3] = ...): pada momen t=150 ms, sleeper masih tidur (sampai 300 ms), waiter masih menunggu sleeper, dan contender masih mengantre lock (sampai ±400 ms). Ketiga pembacaan dilakukan thread main dan disimpan ke array, bukan langsung dicetak.
  6. Baris 53-56 (join() semua): thread main menunggu keempat pekerja benar-benar selesai, baru state worker dibaca ulang — pasti TERMINATED.
  7. Baris 59-61 (for (String baris : catatan)): seluruh catatan dicetak setelah semua join, sehingga urutan keluaran tidak bergantung kecepatan thread mana pun.

State di Aplikasi Nyata #

Ketika aplikasi produksi mengalami kelambatan, langkah diagnosis klasiknya adalah mengambil dump thread (misalnya dengan jstack <pid>). Tumpukan ratusan thread berlabel BLOCKED pada objek yang sama menunjukkan lock contention — banyak thread berebut satu kunci; gundukan thread WAITING pada antrian internal bisa berarti pekerjaan habis atau notifikasi yang tidak pernah datang; thread TIMED_WAITING pada pemanggilan sleep() yang panjang sering menandakan polling yang seharusnya diganti mekanisme menunggu yang tepat. Membaca pola-pola tersebut adalah keterampilan yang tumbuh dari pemahaman diagram transisi di atas.

State NEW dan TERMINATED mengingatkan kita bahwa Thread hanyalah objek Java biasa yang kehidupan eksekusinya pendek dan sekali jalan. Dua properti objek lainnya — prioritas dan status daemon — mengubah cara JVM memperlakukan thread meski tanpa mengubah diagram transisi. Keduanya menjadi topik Chapter berikutnya.

Referensi #

Berkas Sumber

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

SiklusState.java

Unduh berkas
java
public class SiklusState {
    private static final Object kunci = new Object();

    public static void main(String[] args) throws InterruptedException {
        String[] catatan = new String[5];

        // sleeper: tidur 300 ms, selama itu state-nya TIMED_WAITING.
        Thread sleeper = new Thread(() -> {
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "sleeper");

        // worker: memegang lock "kunci" sambil tidur 400 ms.
        Thread worker = new Thread(() -> {
            synchronized (kunci) {
                try {
                    Thread.sleep(400);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }, "worker");

        // waiter: menunggu thread sleeper selesai lewat join tanpa batas waktu.
        Thread waiter = new Thread(() -> {
            try {
                sleeper.join();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "waiter");

        // contender: akan mengantre pada lock "kunci" yang sedang dipegang worker.
        Thread contender = new Thread(() -> {
            synchronized (kunci) {
                // masuk ke sini hanya setelah worker melepas kunci
            }
        }, "contender");

        catatan[0] = "sebelum start   : " + worker.getState(); // NEW

        sleeper.start();
        worker.start();
        Thread.sleep(50); // beri waktu worker mengambil kunci dan sleeper mulai tidur

        waiter.start();
        contender.start();
        Thread.sleep(100); // beri waktu contender mengantre dan waiter menunggu

        catatan[1] = "sedang tidur    : " + sleeper.getState();   // TIMED_WAITING
        catatan[2] = "menunggu join   : " + waiter.getState();    // WAITING
        catatan[3] = "mengantre kunci : " + contender.getState(); // BLOCKED

        sleeper.join();
        waiter.join();
        contender.join();
        worker.join();

        catatan[4] = "setelah selesai : " + worker.getState(); // TERMINATED

        for (String baris : catatan) {
            System.out.println(baris);
        }
    }
}