CodeGym /Java Blog /Acak /Lebih baik bersama: Java dan kelas Thread. Bagian II — Si...
John Squirrels
Level 41
San Francisco

Lebih baik bersama: Java dan kelas Thread. Bagian II — Sinkronisasi

Dipublikasikan di grup Acak

Perkenalan

Jadi, kita tahu bahwa Java memiliki utas. Anda dapat membacanya di ulasan berjudul Lebih baik bersama: Java dan kelas Thread. Bagian I — Utas eksekusi . Utas diperlukan untuk melakukan pekerjaan secara paralel. Hal ini membuat sangat mungkin bahwa utas entah bagaimana akan berinteraksi satu sama lain. Mari kita lihat bagaimana ini terjadi dan alat dasar apa yang kita miliki. Lebih baik bersama: Java dan kelas Thread.  Bagian II — Sinkronisasi - 1

Menghasilkan

Thread.yield() membingungkan dan jarang digunakan. Ini dijelaskan dengan berbagai cara di Internet. Termasuk beberapa orang menulis bahwa ada beberapa antrian utas, di mana utas akan turun berdasarkan prioritas utas. Orang lain menulis bahwa utas akan mengubah statusnya dari "Berjalan" menjadi "Dapat dijalankan" (meskipun tidak ada perbedaan antara status ini, yaitu Java tidak membedakannya). Kenyataannya adalah bahwa itu semua kurang terkenal namun lebih sederhana dalam arti tertentu. Lebih baik bersama: Java dan kelas Thread.  Bagian II — Sinkronisasi - 2Ada bug ( JDK-6416721: (utas spesifikasi) Perbaiki Thread.yield() javadoc ) dicatat untuk yield()dokumentasi metode. Jika Anda membacanya, jelas bahwayield()metode sebenarnya hanya memberikan beberapa rekomendasi kepada penjadwal utas Java agar utas ini dapat diberikan waktu eksekusi yang lebih sedikit. Tetapi apa yang sebenarnya terjadi, yaitu apakah penjadwal bertindak berdasarkan rekomendasi dan apa yang dilakukannya secara umum, bergantung pada implementasi JVM dan sistem operasi. Dan itu mungkin tergantung pada beberapa faktor lain juga. Semua kebingungan kemungkinan besar disebabkan oleh fakta bahwa multithreading telah dipikirkan kembali seiring dengan perkembangan bahasa Java. Baca selengkapnya di ikhtisar di sini: Pengantar Singkat tentang Java Thread.yield() .

Tidur

Sebuah utas dapat tertidur selama eksekusi. Ini adalah jenis interaksi termudah dengan utas lainnya. Sistem operasi yang menjalankan mesin virtual Java yang menjalankan kode Java kami memiliki penjadwal utasnya sendiri . Ini memutuskan utas mana yang akan dimulai dan kapan. Seorang programmer tidak dapat berinteraksi dengan penjadwal ini langsung dari kode Java, hanya melalui JVM. Dia dapat meminta penjadwal untuk menjeda utas untuk sementara waktu, yaitu untuk menidurkannya. Anda dapat membaca lebih lanjut di artikel ini: Thread.sleep() dan How Multithreading works . Anda juga dapat melihat cara kerja utas di sistem operasi Windows: Internals of Windows Thread . Dan sekarang mari kita lihat dengan mata kepala sendiri. Simpan kode berikut dalam file bernama HelloWorldApp.java:

class HelloWorldApp {
    public static void main(String []args) {
        Runnable task = () -> {
            try {
                int secToWait = 1000 * 60;
                Thread.currentThread().sleep(secToWait);
                System.out.println("Woke up");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(task);
        thread.start();
    }
}
Seperti yang Anda lihat, kami memiliki beberapa tugas yang menunggu selama 60 detik, setelah itu program berakhir. Kami mengkompilasi menggunakan perintah " javac HelloWorldApp.java" dan kemudian menjalankan program menggunakan " java HelloWorldApp". Yang terbaik adalah memulai program di jendela terpisah. Misalnya di Windows, seperti ini: start java HelloWorldApp. Kami menggunakan perintah jps untuk mendapatkan PID (ID proses), dan kami membuka daftar utas dengan " jvisualvm --openpid pid: Lebih baik bersama: Java dan kelas Thread.  Bagian II — Sinkronisasi - 3Seperti yang Anda lihat, utas kami sekarang memiliki status "Tidur". Bahkan, ada cara yang lebih elegan untuk membantu utas kami memiliki mimpi indah:

try {
	TimeUnit.SECONDS.sleep(60);
	System.out.println("Woke up");
} catch (InterruptedException e) {
	e.printStackTrace();
}
Apakah Anda memperhatikan bahwa kami menangani InterruptedExceptiondi mana-mana? Mari kita pahami alasannya.

Utas.interrupt()

Masalahnya adalah saat utas sedang menunggu/tidur, seseorang mungkin ingin menyela. Dalam hal ini, kami menangani file InterruptedException. Mekanisme ini dibuat setelah Thread.stop()metode tersebut dinyatakan Deprecated, yaitu kedaluwarsa dan tidak diinginkan. Alasannya adalah ketika stop()metode dipanggil, utasnya hanya "dibunuh", yang sangat tidak dapat diprediksi. Kami tidak dapat mengetahui kapan utas akan dihentikan, dan kami tidak dapat menjamin konsistensi data. Bayangkan Anda sedang menulis data ke file saat utas dimatikan. Daripada mematikan utas, pembuat Java memutuskan bahwa akan lebih logis untuk mengatakan bahwa utas harus diinterupsi. Bagaimana menanggapi informasi ini adalah masalah yang diputuskan oleh utas itu sendiri. Untuk detail selengkapnya, baca Mengapa Thread.stop tidak digunakan lagi?di situs web Oracle. Mari kita lihat sebuah contoh:

public static void main(String []args) {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(60);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
Dalam contoh ini, kami tidak akan menunggu 60 detik. Sebagai gantinya, kami akan segera menampilkan "Terganggu". Ini karena kami memanggil interrupt()metode di utas. Metode ini menyetel bendera internal yang disebut "status interupsi". Artinya, setiap utas memiliki bendera internal yang tidak dapat diakses secara langsung. Tapi kami memiliki metode asli untuk berinteraksi dengan bendera ini. Tapi itu bukan satu-satunya cara. Utas mungkin sedang berjalan, tidak menunggu sesuatu, hanya melakukan tindakan. Tetapi mungkin mengantisipasi bahwa orang lain ingin mengakhiri pekerjaannya pada waktu tertentu. Misalnya:

public static void main(String []args) {
	Runnable task = () -> {
		while(!Thread.currentThread().isInterrupted()) {
			// Do some work
		}
		System.out.println("Finished");
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
Pada contoh di atas, whileloop akan dijalankan hingga utas diinterupsi secara eksternal. Sedangkan untuk isInterruptedflag, penting untuk diketahui bahwa jika kita menangkap sebuah InterruptedException, flag isInterrupted akan direset, dan kemudian isInterrupted()mengembalikan false. Kelas Thread juga memiliki metode Thread.interrupted() statis yang hanya berlaku untuk thread saat ini, tetapi metode ini menyetel ulang flag ke false! Baca lebih lanjut dalam bab ini berjudul Thread Interruption .

Gabung (Tunggu utas lainnya selesai)

Jenis penantian yang paling sederhana adalah menunggu utas lainnya selesai.

public static void main(String []args) throws InterruptedException {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(5);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.join();
	System.out.println("Finished");
}
Dalam contoh ini, utas baru akan tidur 5 detik. Pada saat yang sama, utas utama akan menunggu hingga utas tidur bangun dan menyelesaikan pekerjaannya. Jika Anda melihat status utas di JVisualVM, maka akan terlihat seperti ini: Lebih baik bersama: Java dan kelas Thread.  Bagian II — Sinkronisasi - 4Berkat alat pemantauan, Anda dapat melihat apa yang terjadi dengan utas. Metodenya joincukup sederhana, karena ini hanyalah metode dengan kode Java yang dijalankan wait()selama utas yang dipanggil masih hidup. Segera setelah utas mati (ketika selesai dengan pekerjaannya), penantian terputus. Dan itu semua keajaiban dari join()metode ini. Jadi, mari beralih ke hal yang paling menarik.

Memantau

Multithreading mencakup konsep monitor. Kata monitor datang ke bahasa Inggris melalui bahasa Latin abad ke-16 dan berarti "instrumen atau perangkat yang digunakan untuk mengamati, memeriksa, atau menyimpan catatan berkelanjutan dari suatu proses". Dalam konteks artikel ini, kami akan mencoba membahas dasar-dasarnya. Bagi siapa pun yang menginginkan detailnya, silakan selami materi yang ditautkan. Kami memulai perjalanan kami dengan Spesifikasi Bahasa Jawa (JLS): 17.1. Sinkronisasi . Dikatakan sebagai berikut: Lebih baik bersama: Java dan kelas Thread.  Bagian II — Sinkronisasi - 5Ternyata Java menggunakan mekanisme "monitor" untuk sinkronisasi antar utas. Monitor dikaitkan dengan setiap objek, dan utas dapat memperolehnya dengan lock()atau melepaskannya dengan unlock(). Selanjutnya, kita akan menemukan tutorialnya di website Oracle: Intrinsic Locks and Synchronization. Tutorial ini mengatakan bahwa sinkronisasi Java dibangun di sekitar entitas internal yang disebut kunci intrinsik atau monitor lock . Kunci ini sering disebut " monitor ". Kami juga melihat lagi bahwa setiap objek di Java memiliki kunci intrinsik yang terkait dengannya. Anda dapat membaca Java - Intrinsic Locks and Synchronization . Selanjutnya, penting untuk memahami bagaimana sebuah objek di Java dapat diasosiasikan dengan monitor. Di Java, setiap objek memiliki header yang menyimpan metadata internal yang tidak tersedia untuk pemrogram dari kode, tetapi mesin virtual perlu bekerja dengan objek dengan benar. Header objek menyertakan "kata tanda", yang terlihat seperti ini: Lebih baik bersama: Java dan kelas Thread.  Bagian II — Sinkronisasi - 6

https://edu.netbeans.org/contrib/slides/java-overview-and-java-se6.pdf

Inilah artikel JavaWorld yang sangat berguna: Bagaimana mesin virtual Java melakukan sinkronisasi utas . Artikel ini harus digabungkan dengan deskripsi dari bagian "Ringkasan" dari masalah berikut dalam sistem pelacakan bug JDK: JDK-8183909 . Anda dapat membaca hal yang sama di sini: JEP-8183909 . Jadi, di Java, monitor dikaitkan dengan objek dan digunakan untuk memblokir utas saat utas mencoba mendapatkan (atau mendapatkan) kunci. Inilah contoh paling sederhana:

public class HelloWorld{
    public static void main(String []args){
        Object object = new Object();
        synchronized(object) {
            System.out.println("Hello World");
        }
    }
}
Di sini, utas saat ini (utas tempat baris kode ini dieksekusi) menggunakan synchronizedkata kunci untuk mencoba menggunakan monitor yang terkait denganobject"\variabel untuk mendapatkan/memperoleh kunci. Jika tidak ada orang lain yang memperebutkan monitor (yaitu tidak ada orang lain yang menjalankan kode tersinkronisasi menggunakan objek yang sama), maka Java dapat mencoba melakukan pengoptimalan yang disebut "penguncian bias". Tag yang relevan dan catatan tentang utas mana yang memiliki kunci monitor ditambahkan ke kata tanda di header objek. Ini mengurangi biaya overhead yang diperlukan untuk mengunci monitor. Jika monitor sebelumnya dimiliki oleh utas lain, penguncian seperti itu tidak cukup. JVM beralih ke jenis penguncian berikutnya: "penguncian dasar". Ini menggunakan operasi bandingkan dan tukar (CAS). Terlebih lagi, kata tanda header objek itu sendiri tidak lagi menyimpan kata tanda, melainkan referensi ke tempat penyimpanannya, dan tag berubah sehingga JVM memahami bahwa kita menggunakan penguncian dasar. Jika beberapa utas bersaing (bersaing) untuk monitor (satu telah memperoleh kunci, dan yang kedua menunggu kunci dilepaskan), maka tag dalam kata tanda berubah, dan kata tanda sekarang menyimpan referensi ke monitor sebagai objek — beberapa entitas internal JVM. Sebagaimana dinyatakan dalam JDK Enchancement Proposal (JEP), situasi ini memerlukan ruang di area memori Native Heap untuk menyimpan entitas ini. Referensi ke lokasi memori entitas internal ini akan disimpan dalam kata tanda header objek. Jadi, monitor sebenarnya adalah mekanisme untuk menyinkronkan akses ke sumber daya bersama di antara banyak utas. JVM beralih di antara beberapa implementasi mekanisme ini. Jadi, untuk mempermudah, saat berbicara tentang monitor, sebenarnya kita sedang berbicara tentang kunci. dan sedetik menunggu kunci dilepaskan), kemudian tag pada kata tanda berubah, dan kata tanda sekarang menyimpan referensi ke monitor sebagai objek - beberapa entitas internal JVM. Sebagaimana dinyatakan dalam JDK Enchancement Proposal (JEP), situasi ini memerlukan ruang di area memori Native Heap untuk menyimpan entitas ini. Referensi ke lokasi memori entitas internal ini akan disimpan dalam kata tanda header objek. Jadi, monitor sebenarnya adalah mekanisme untuk menyinkronkan akses ke sumber daya bersama di antara banyak utas. JVM beralih di antara beberapa implementasi mekanisme ini. Jadi, untuk mempermudah, saat berbicara tentang monitor, sebenarnya kita sedang berbicara tentang kunci. dan sedetik menunggu kunci dilepaskan), kemudian tag pada kata tanda berubah, dan kata tanda sekarang menyimpan referensi ke monitor sebagai objek - beberapa entitas internal JVM. Sebagaimana dinyatakan dalam JDK Enchancement Proposal (JEP), situasi ini memerlukan ruang di area memori Native Heap untuk menyimpan entitas ini. Referensi ke lokasi memori entitas internal ini akan disimpan dalam kata tanda header objek. Jadi, monitor sebenarnya adalah mekanisme untuk menyinkronkan akses ke sumber daya bersama di antara banyak utas. JVM beralih di antara beberapa implementasi mekanisme ini. Jadi, untuk mempermudah, saat berbicara tentang monitor, sebenarnya kita sedang berbicara tentang kunci. dan kata tanda sekarang menyimpan referensi ke monitor sebagai objek — beberapa entitas internal JVM. Sebagaimana dinyatakan dalam JDK Enchancement Proposal (JEP), situasi ini memerlukan ruang di area memori Native Heap untuk menyimpan entitas ini. Referensi ke lokasi memori entitas internal ini akan disimpan dalam kata tanda header objek. Jadi, monitor sebenarnya adalah mekanisme untuk menyinkronkan akses ke sumber daya bersama di antara banyak utas. JVM beralih di antara beberapa implementasi mekanisme ini. Jadi, untuk mempermudah, saat berbicara tentang monitor, sebenarnya kita sedang berbicara tentang kunci. dan kata tanda sekarang menyimpan referensi ke monitor sebagai objek — beberapa entitas internal JVM. Sebagaimana dinyatakan dalam JDK Enchancement Proposal (JEP), situasi ini memerlukan ruang di area memori Native Heap untuk menyimpan entitas ini. Referensi ke lokasi memori entitas internal ini akan disimpan dalam kata tanda header objek. Jadi, monitor sebenarnya adalah mekanisme untuk menyinkronkan akses ke sumber daya bersama di antara banyak utas. JVM beralih di antara beberapa implementasi mekanisme ini. Jadi, untuk mempermudah, saat berbicara tentang monitor, sebenarnya kita sedang berbicara tentang kunci. Referensi ke lokasi memori entitas internal ini akan disimpan dalam kata tanda header objek. Jadi, monitor sebenarnya adalah mekanisme untuk menyinkronkan akses ke sumber daya bersama di antara banyak utas. JVM beralih di antara beberapa implementasi mekanisme ini. Jadi, untuk mempermudah, saat berbicara tentang monitor, sebenarnya kita sedang berbicara tentang kunci. Referensi ke lokasi memori entitas internal ini akan disimpan dalam kata tanda header objek. Jadi, monitor sebenarnya adalah mekanisme untuk menyinkronkan akses ke sumber daya bersama di antara banyak utas. JVM beralih di antara beberapa implementasi mekanisme ini. Jadi, untuk mempermudah, saat berbicara tentang monitor, sebenarnya kita sedang berbicara tentang kunci. Lebih baik bersama: Java dan kelas Thread.  Bagian II — Sinkronisasi - 7

Disinkronkan (menunggu kunci)

Seperti yang kita lihat sebelumnya, konsep "blok tersinkronisasi" (atau "bagian penting") terkait erat dengan konsep monitor. Lihatlah contoh:

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

	Runnable task = () -> {
		synchronized(lock) {
			System.out.println("thread");
		}
	};

	Thread th1 = new Thread(task);
	th1.start();
	synchronized(lock) {
		for (int i = 0; i < 8; i++) {
			Thread.currentThread().sleep(1000);
			System.out.print(" " + i);
		}
		System.out.println(" ...");
	}
}
Di sini, utas utama pertama-tama meneruskan objek tugas ke utas baru, lalu segera memperoleh kunci dan melakukan operasi panjang dengannya (8 detik). Selama ini tugas tidak bisa dilanjutkan, karena tidak bisa masuk synchronizedblok, karena kunci sudah didapat. Jika utas tidak bisa mendapatkan kunci, itu akan menunggu monitor. Segera setelah mendapat kunci, itu akan melanjutkan eksekusi. Saat utas keluar dari monitor, utas itu melepaskan kuncinya. Di JVisualVM, tampilannya seperti ini: Lebih baik bersama: Java dan kelas Thread.  Bagian II — Sinkronisasi - 8Seperti yang Anda lihat di JVisualVM, statusnya adalah "Monitor", artinya utas diblokir dan tidak dapat menggunakan monitor. Anda juga dapat menggunakan kode untuk menentukan status utas, tetapi nama status yang ditentukan dengan cara ini tidak cocok dengan nama yang digunakan di JVisualVM, meskipun serupa. Dalam hal ini,th1.getState()pernyataan dalam for loop akan mengembalikan BLOCKED , karena selama loop berjalan, lockmonitor objek ditempati oleh mainutas, dan th1utas diblokir dan tidak dapat dilanjutkan hingga kunci dilepaskan. Selain blok yang disinkronkan, seluruh metode dapat disinkronkan. Misalnya, inilah metode dari HashTablekelas:

public synchronized int size() {
	return count;
}
Metode ini akan dieksekusi hanya oleh satu utas pada waktu tertentu. Apakah kita benar-benar membutuhkan kunci? Ya, kami membutuhkannya. Dalam kasus metode instan, objek "ini" (objek saat ini) bertindak sebagai kunci. Ada diskusi menarik tentang topik ini di sini: Apakah ada keuntungan menggunakan Metode Tersinkronisasi daripada Blok Tersinkronisasi? . Jika metodenya statis, maka kuncinya bukan objek "ini" (karena tidak ada objek "ini" untuk metode statis), melainkan objek Kelas (misalnya, ) Integer.class.

Tunggu (menunggu monitor). notify() dan notifyAll() metode

Kelas Thread memiliki metode menunggu lain yang diasosiasikan dengan monitor. Tidak seperti sleep()and join(), metode ini tidak bisa dipanggil begitu saja. Namanya adalah wait(). Metode waitdipanggil pada objek yang terkait dengan monitor yang ingin kita tunggu. Mari kita lihat contohnya:

public static void main(String []args) throws InterruptedException {
	    Object lock = new Object();
	    // The task object will wait until it is notified via lock
	    Runnable task = () -> {
	        synchronized(lock) {
	            try {
	                lock.wait();
	            } catch(InterruptedException e) {
	                System.out.println("interrupted");
	            }
	        }
	        // After we are notified, we will wait until we can acquire the lock
	        System.out.println("thread");
	    };
	    Thread taskThread = new Thread(task);
	    taskThread.start();
        // We sleep. Then we acquire the lock, notify, and release the lock
	    Thread.currentThread().sleep(3000);
	    System.out.println("main");
	    synchronized(lock) {
	        lock.notify();
	    }
}
Di JVisualVM, tampilannya seperti ini: Lebih baik bersama: Java dan kelas Thread.  Bagian II — Sinkronisasi - 10Untuk memahami cara kerjanya, ingatlah bahwa metode wait()dan notify()dikaitkan dengan java.lang.Object. Mungkin tampak aneh bahwa metode terkait utas ada di Objectkelas. Tapi alasan untuk itu sekarang terungkap. Anda akan ingat bahwa setiap objek di Java memiliki header. Header berisi berbagai informasi tata graha, termasuk informasi tentang monitor, yaitu status kunci. Ingat, setiap objek, atau turunan dari kelas, dikaitkan dengan entitas internal di JVM, yang disebut kunci atau monitor intrinsik. Pada contoh di atas, kode untuk objek tugas menunjukkan bahwa kita memasukkan blok tersinkronisasi untuk monitor yang terkait dengan lockobjek tersebut. Jika kami berhasil mendapatkan kunci untuk monitor ini, makawait()disebut. Utas yang menjalankan tugas akan melepaskan lockmonitor objek, tetapi akan memasuki antrean utas menunggu pemberitahuan dari lockmonitor objek. Antrian utas ini disebut SET TUNGGU, yang lebih tepat mencerminkan tujuannya. Artinya, ini lebih merupakan kumpulan daripada antrian. Utas mainmembuat utas baru dengan objek tugas, memulainya, dan menunggu 3 detik. Hal ini sangat mungkin membuat utas baru dapat memperoleh kunci sebelum utas main, dan masuk ke antrean monitor. Setelah itu, mainutas itu sendiri memasuki lockblok objek yang disinkronkan dan melakukan pemberitahuan utas menggunakan monitor. Setelah pemberitahuan dikirim, mainutas melepaskanlockmonitor objek, dan utas baru, yang sebelumnya menunggu lockmonitor objek dirilis, melanjutkan eksekusi. Dimungkinkan untuk mengirim pemberitahuan hanya ke satu utas ( notify()) atau secara bersamaan ke semua utas dalam antrean ( notifyAll()). Baca lebih lanjut di sini: Perbedaan antara notify() dan notifyAll() di Java . Penting untuk diperhatikan bahwa urutan notifikasi tergantung pada bagaimana JVM diimplementasikan. Baca lebih lanjut di sini: Bagaimana cara mengatasi kelaparan dengan notify dan notifyAll? . Sinkronisasi dapat dilakukan tanpa menentukan objek. Anda dapat melakukan ini saat seluruh metode disinkronkan daripada satu blok kode. Misalnya, untuk metode statis, kuncinya akan berupa objek Kelas (diperoleh melalui .class):

public static synchronized void printA() {
	System.out.println("A");
}
public static void printB() {
	synchronized(HelloWorld.class) {
		System.out.println("B");
	}
}
Dalam hal penggunaan kunci, kedua metode itu sama. Jika suatu metode tidak statis, maka sinkronisasi akan dilakukan dengan menggunakan current instance, yaitu menggunakan this. Omong-omong, kami katakan sebelumnya Anda dapat menggunakan getState()metode ini untuk mendapatkan status utas. Misalnya, untuk utas dalam antrean yang menunggu monitor, statusnya akan WAITING atau TIMED_WAITING, jika wait()metode tersebut menetapkan batas waktu. Lebih baik bersama: Java dan kelas Thread.  Bagian II — Sinkronisasi - 11

https://stackoverflow.com/questions/36425942/what-is-the-lifecycle-of-thread-in-java

Siklus hidup benang

Sepanjang hidupnya, status utas berubah. Faktanya, perubahan ini terdiri dari siklus hidup utas. Segera setelah utas dibuat, statusnya adalah BARU. Dalam keadaan ini, utas baru belum berjalan dan penjadwal utas Java belum mengetahui apa pun tentangnya. Agar penjadwal utas mempelajari tentang utas, Anda harus memanggil thread.start()metode. Kemudian utas akan bertransisi ke status DAPAT DIJALANKAN. Internet memiliki banyak diagram yang salah yang membedakan antara status "Dapat dijalankan" dan "Berjalan". Tapi ini kesalahan, karena Java tidak membedakan antara "ready to work" (runnable) dan "working" (running). Saat utas hidup tetapi tidak aktif (tidak dapat dijalankan), utas berada dalam salah satu dari dua status:
  • DIBLOKIR — menunggu untuk memasuki bagian kritis, yaitu synchronizedblok.
  • MENUNGGU — menunggu utas lain untuk memenuhi beberapa kondisi.
Jika kondisi terpenuhi, maka penjadwal utas akan memulai utas. Jika utas menunggu hingga waktu yang ditentukan, maka statusnya adalah TIMED_WAITING. Jika utas tidak lagi berjalan (sudah selesai atau pengecualian dilemparkan), maka utas akan memasuki status DIHENTIKAN. Untuk mengetahui status utas, gunakan getState()metode ini. Utas juga memiliki isAlive()metode, yang mengembalikan true jika utas tidak DIHENTIKAN.

LockSupport dan parkir utas

Dimulai dengan Java 1.6, mekanisme menarik yang disebut LockSupport muncul. Lebih baik bersama: Java dan kelas Thread.  Bagian II — Sinkronisasi - 12Kelas ini mengaitkan "izin" dengan setiap utas yang menggunakannya. Panggilan ke park()metode segera kembali jika izin tersedia, menggunakan izin dalam proses. Jika tidak, itu memblokir. Memanggil unparkmetode membuat izin tersedia jika belum tersedia. Hanya ada 1 izin. Dokumentasi Java untuk LockSupportmerujuk ke Semaphorekelas. Mari kita lihat contoh sederhana:

import java.util.concurrent.Semaphore;
public class HelloWorldApp{
    
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(0);
        try {
            semaphore.acquire();
        } catch (InterruptedException e) {
            // Request the permit and wait until we get it
            e.printStackTrace();
        }
        System.out.println("Hello, World!");
    }
}
Kode ini akan selalu menunggu, karena sekarang semafor memiliki 0 izin. Dan ketika acquire()dipanggil dalam kode (yaitu meminta izin), utas menunggu hingga menerima izin. Karena kita sedang menunggu, kita harus menanganinya InterruptedException. Menariknya, semafor mendapat status utas terpisah. Jika kita melihat di JVisualVM, kita akan melihat bahwa statusnya bukan "Tunggu", tetapi "Park". Lebih baik bersama: Java dan kelas Thread.  Bagian II — Sinkronisasi - 13Mari kita lihat contoh lain:

public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            // Park the current thread
            System.err.println("Will be Parked");
            LockSupport.park();
            // As soon as we are unparked, we will start to act
            System.err.println("Unparked");
        };
        Thread th = new Thread(task);
        th.start();
        Thread.currentThread().sleep(2000);
        System.err.println("Thread state: " + th.getState());
        
        LockSupport.unpark(th);
        Thread.currentThread().sleep(2000);
}
Status utas akan MENUNGGU, tetapi JVisualVM membedakan antara waitdari synchronizedkata kunci dan parkdari LockSupportkelas. Mengapa ini LockSupportsangat penting? Kami kembali ke dokumentasi Java dan melihat status WAITING thread. Seperti yang Anda lihat, hanya ada tiga cara untuk masuk ke dalamnya. Dua di antaranya adalah wait()dan join(). Dan yang ketiga adalah LockSupport. Di Java, kunci juga dapat dibangun di LockSupport dan menawarkan alat dengan tingkat yang lebih tinggi. Mari kita coba gunakan salah satunya. Sebagai contoh, lihatlah ReentrantLock:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class HelloWorld{

    public static void main(String []args) throws InterruptedException {
        Lock lock = new ReentrantLock();
        Runnable task = () -> {
            lock.lock();
            System.out.println("Thread");
            lock.unlock();
        };
        lock.lock();

        Thread th = new Thread(task);
        th.start();
        System.out.println("main");
        Thread.currentThread().sleep(2000);
        lock.unlock();
    }
}
Sama seperti pada contoh sebelumnya, semuanya sederhana di sini. Objek lockmenunggu seseorang melepaskan sumber daya bersama. Jika kita melihat di JVisualVM, kita akan melihat bahwa utas baru akan diparkir hingga utas mainmelepaskan kuncinya. Anda dapat membaca lebih lanjut tentang kunci di sini: Java 8 StampedLocks vs. ReadWriteLocks dan Synchronized and Lock API in Java. Untuk lebih memahami bagaimana kunci diimplementasikan, ada gunanya membaca tentang Phaser di artikel ini: Panduan untuk Java Phaser . Dan berbicara tentang berbagai sinkronisasi, Anda harus membaca artikel DZone di The Java Synchronizers.

Kesimpulan

Dalam ulasan ini, kami memeriksa cara utama utas berinteraksi di Jawa. Material tambahan: Lebih baik bersama: Java dan kelas Thread. Bagian I — Utas eksekusi Lebih baik bersama: Java dan kelas Utas. Bagian III — Interaksi Bersama yang lebih baik: Java dan kelas Thread. Bagian IV — Callable, Future, dan teman Lebih baik bersama: Java dan kelas Thread. Bagian V — Pelaksana, ThreadPool, Fork/Bergabung Lebih baik bersama-sama: Java dan kelas Thread. Bagian VI — Tembak!
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION