Seri ini didasarkan pada jalannya penyulingan menjadi emas, dan untuk belajar lebih baik, serangkaian catatan dibuat. Artikel ini terutama memperkenalkan 1. Apa itu utas 2. Operasi dasar utas 3. Daemon Thread 4. Thread Prioritas 5. Operasi Sinkronisasi Thread Dasar
1. Apa utas
Thread adalah unit eksekusi dalam proses
Ada beberapa utas dalam suatu proses.
Utas adalah unit eksekusi dalam suatu proses.
Alasan untuk menggunakan utas adalah bahwa switching proses adalah operasi kelas yang sangat berat dan mengkonsumsi sumber daya. Jika Anda menggunakan banyak proses, jumlah konkurensi tidak akan terlalu tinggi. Utas adalah unit penjadwalan yang lebih kecil dan lebih ringan, sehingga utas lebih banyak digunakan dalam desain bersamaan.
Di Java, konsep utas mirip dengan konsep utas tingkat sistem operasi. Bahkan, JVM akan memetakan utas di Java ke area utas sistem operasi.
2. Operasi dasar utas
2.1 Diagram Status Thread
Gambar di atas menunjukkan operasi dasar utas di java.
Ketika utas baru, utas sebenarnya tidak berfungsi. Ini hanya menghasilkan entitas, dan utasnya sebenarnya dimulai ketika Anda memanggil metode awal dari contoh ini. Setelah startup, ia mencapai keadaan yang dapat dijalankan. Runnable berarti bahwa sumber daya utas dan sebagainya telah disiapkan dan dapat dieksekusi, tetapi itu tidak berarti bahwa itu dalam keadaan eksekusi. Karena rotasi irisan waktu, utas mungkin tidak dieksekusi saat ini. Bagi kami, utas dapat dianggap telah dieksekusi, tetapi apakah dieksekusi sebenarnya tergantung pada penjadwalan CPU fisik. Ketika tugas utas dieksekusi, utas mencapai keadaan yang diakhiri.
Kadang -kadang, selama pelaksanaan utas, tidak dapat dihindari bahwa beberapa kunci atau monitor objek akan diterapkan. Ketika tidak dapat diambil, utas akan diblokir, ditangguhkan, dan mencapai keadaan yang diblokir. Jika utas ini memanggil metode tunggu, itu dalam keadaan menunggu. Utas yang memasuki status menunggu akan menunggu utas lain untuk memberitahukannya. Setelah pemberitahuan dibuat, keadaan menunggu akan beralih dari keadaan menunggu ke keadaan yang dapat dilalui untuk melanjutkan eksekusi. Tentu saja, ada dua jenis negara bagian, satu harus menunggu tanpa batas waktu sampai diberitahukan. Telah menunggu jangka waktu terbatas. Misalnya, jika Anda menunggu 10 detik atau belum diberitahu, itu akan secara otomatis beralih ke keadaan runnable.
2.2 Buat utas baru
Thread thread = utas baru ();
thread.start ();
Ini membuka utas.
Satu hal yang perlu diperhatikan adalah
Thread thread = utas baru ();
thread.run ();
Anda tidak dapat membuka utas baru dengan memanggil metode RUN secara langsung.
Metode start sebenarnya memanggil metode run pada utas sistem operasi baru. Dengan kata lain, jika Anda memanggil metode run secara langsung alih -alih metode start, itu tidak akan memulai utas baru, tetapi akan melakukan operasi Anda di run panggilan utas saat ini.
Thread thread = utas baru ("T1") {@Override public void run () {// TODO Metode yang dihasilkan otomatis Stub System.out.println (thread.currentThread (). GetName ()); }}; thread.start (); Jika start dipanggil, outputnya adalah t1thread thread = utas baru ("t1") {@Override public void run () {// TODO Metode Stub yang dihasilkan secara otomatis System.out.println (thread.currentThread (). GetName ()); }}; thread.run (); Jika dijalankan, utama adalah output. (Mengubah langsung sebenarnya hanyalah panggilan fungsi biasa, dan belum mencapai peran multi-threading)
Ada dua cara untuk mengimplementasikan metode run
Metode pertama adalah secara langsung mengganti metode run. Seperti yang ditunjukkan dalam kode sekarang, cara yang paling nyaman dapat dicapai dengan menggunakan kelas anonim.
Thread thread = utas baru ("T1") {@Override public void run () {// TODO Metode yang dihasilkan otomatis Stub System.out.println (thread.currentThread (). GetName ()); }}; Cara kedua
Thread t1 = utas baru (createThread3 baru ());
CreateThread3 () mengimplementasikan antarmuka yang dapat dijalankan.
Dalam video Zhang Xiaoxiang, metode kedua direkomendasikan, mengatakan bahwa itu lebih berorientasi objek.
2.3 Teramkan utas
Thread.stop () tidak disarankan. Itu melepaskan semua monitor
Telah dinyatakan dengan jelas dalam kode sumber bahwa metode berhenti sudah usang, dan alasannya juga dijelaskan di Javadoc.
Alasannya adalah bahwa metode berhenti terlalu "kekerasan". Di mana pun utas dieksekusi, itu akan segera berhenti menjatuhkan utas.
Ketika utas tulis mendapatkan kunci, mulai menulis data. Setelah menulis ID = 1, dihentikan dan kunci dilepaskan saat bersiap untuk mengatur nama = 1. Utas baca mendapatkan kunci untuk operasi membaca. ID yang dibaca adalah 1 dan namanya masih 0, yang menyebabkan ketidakkonsistenan data.
Yang paling penting adalah bahwa kesalahan semacam ini tidak akan melempar pengecualian dan akan sulit dideteksi.
2.4 Interrupt Thread
Ada 3 cara untuk mengganggu utas
public void thread.interrupt () // interrupt thread
utas boolean publik.
Public Static Boolean Thread.Interrupted () // Tentukan apakah itu terganggu dan bersihkan status interupsi saat ini
Apa itu gangguan utas?
Jika Anda tidak memahami mekanisme interupsi Java, penjelasan seperti itu sangat mungkin menyebabkan kesalahpahaman, dan Anda percaya bahwa memanggil metode interupsi utas pasti akan mengganggu utas.
Faktanya, gangguan Java adalah mekanisme kolaborasi. Dengan kata lain, memanggil metode interupsi dari objek utas tidak selalu mengganggu utas yang sedang berjalan, itu hanya membutuhkan utas untuk mengganggu dirinya sendiri pada waktu yang tepat. Setiap utas memiliki keadaan interupsi Boolean (tidak harus properti objek, pada kenyataannya, keadaan ini memang bukan bidang utas), dan metode interupsi hanya mengatur negara menjadi benar. Untuk utas yang tidak memblokir, keadaan interupsi diubah, yaitu thread.isinterrupted () akan mengembalikan true dan tidak akan menghentikan program;
public void run () {// thread t1 while (true) {thread.yield (); }} t1.interrupt (); Ini tidak akan memiliki efek untuk mengganggu utas T1, tetapi bit status interupsi diubah.
Jika Anda ingin mengakhiri utas ini dengan sangat anggun, Anda harus melakukan ini
public void run () {while (true) {if (thread.currentThread (). isInterrupted ()) {System.out.println ("Diinterruted!"); merusak; } Thread.yield (); }}Menggunakan interupsi memberikan jaminan tertentu untuk konsistensi data.
Untuk utas dalam status pemblokiran yang dapat dibatalkan, seperti utas yang menunggu pada fungsi -fungsi ini, thread.sleep (), objek.wait (), dan thread.join (), utas ini akan melempar Exception interrupted setelah menerima sinyal interupsi, dan akan mengatur status interupsi kembali ke false.
Untuk utas dalam status pemblokiran, Anda dapat menulis kode seperti ini:
public void run () {while (true) {if (thread.currentThread (). isInterrupted ()) {System.out.println ("Diinterruted!"); merusak; } coba {thread.sleep (2000); } catch (InterruptedException e) {System.out.println ("Diinterruting saat tidur"); // Atur status interupsi. Bit bendera interupsi akan dihapus setelah melempar utas pengecualian. CurrentThread (). Interrupt (); } Thread.yield (); }}2.5 Thread Hang
Menangguhkan dan melanjutkan utas
suspend () tidak akan melepaskan kunci
Jika penguncian terjadi sebelum resume (), kedua metode kebuntuan terjadi adalah metode yang sudah usang dan tidak disarankan.
Alasannya adalah bahwa Suspend tidak melepaskan kunci, sehingga tidak ada utas yang dapat mengakses sumber daya area kritis yang dikunci olehnya sampai dilanjutkan oleh utas lain. Karena urutan utas yang berjalan tidak dapat dikontrol, jika metode resume utas lain dijalankan terlebih dahulu, yang ditangguhkan nanti akan selalu menempati kunci, menyebabkan kebuntuan terjadi.
Gunakan kode berikut untuk mensimulasikan skenario ini
tes paket; tes kelas publik {objek statis u = objek baru (); TestsuspendThread T1 = testsuspendThread baru ("T1"); TestsuspendThread statis T2 = testsuspendThread baru ("T2"); tes kelas statis publicSuspendThread memperluas utas {public testsuspendThread (nama string) {setName (name); } @Override public void run () {disinkronkan (u) {System.out.println ("di" + getName ()); Thread.currentThread (). Suspend (); }} public static void main (string [] args) melempar interruptedException {t1.start (); Thread.sleep (100); t2.start (); t1.resume (); t2.resume (); t1.join (); t2.join (); }} Biarkan T1 dan T2 bersaing untuk mengunci pada saat yang sama, utas yang bersaing ditangguhkan, dan kemudian dilanjutkan. Secara logis, utas harus dirilis dengan resume setelah bersaing untuk itu, dan kemudian utas lain bersaing untuk mengunci dan melanjutkan.
Output hasilnya adalah:
di t1
di T2
Ini berarti bahwa kedua utas bersaing untuk kunci, tetapi lampu merah pada konsol masih menyala, yang berarti bahwa harus ada utas yang belum dieksekusi dalam T1 dan T2. Mari kita buang dan lihatlah
Ditemukan bahwa T2 telah ditangguhkan. Ini menciptakan kebuntuan.
2.6 Bergabung dan Anda
Yeild adalah metode statis asli. Metode ini adalah untuk merilis waktu CPU yang dimilikinya dan kemudian bersaing dengan utas lain (perhatikan bahwa utas Yeild masih dapat bersaing untuk CPU, perhatikan perbedaan dari tidur). Juga dijelaskan dalam Javadoc bahwa Yeild adalah metode yang pada dasarnya tidak digunakan dan umumnya digunakan dalam debug dan tes.
Metode bergabung berarti menunggu utas lain berakhir, seperti kode di bagian Suspend, Anda ingin utas utama menunggu T1 dan T2 berakhir sebelum berakhir. Jika tidak berakhir, utas utama akan diblokir di sana.
tes paket; tes kelas publik {public volatile static int i = 0; kelas public static addThread memperluas utas {@Override public void run () {for (i = 0; i <10000000; i ++); }} public static void main (string [] args) melempar interruptedException {addThread at = new addThread (); at.start (); at.join (); System.out.println (i); }} Jika AT. Memberikan kode di atas dihapus, utas utama akan berjalan secara langsung dan nilai saya akan sangat kecil. Jika ada gabungan, nilai cetak saya harus 10000000.
Jadi bagaimana gabungan diimplementasikan?
Sifat bergabung
while (isAlive ())
{
tunggu (0);
}
Metode gabungan () juga dapat melewati waktu, yang berarti menunggu periode waktu yang terbatas, dan secara otomatis bangun setelah waktu ini.
Ada pertanyaan seperti ini: Siapa yang akan memberi tahu utasnya? Tidak ada tempat untuk menelepon Notify di kelas utas?
Di Javadoc, penjelasan yang relevan ditemukan. Ketika utas selesai dan diakhiri, metode notifyall akan dipanggil untuk membangunkan semua utas yang menunggu pada instance utas saat ini. Operasi ini dilakukan oleh JVM itu sendiri.
Jadi Javadoc juga memberi kami saran untuk tidak menggunakan tunggu dan memberi tahu/memberi tahu semua tentang instance utas. Karena JVM akan menelepon dirinya sendiri, itu mungkin berbeda dari hasil yang diharapkan dari panggilan Anda.
3. Benang daemon
Diam -diam menyelesaikan beberapa layanan sistematis di latar belakang, seperti utas pengumpulan sampah dan utas JIT, dapat dipahami sebagai benang daemon.
Ketika semua proses non-daemon berakhir dalam aplikasi Java, mesin virtual Java akan keluar secara alami.
Saya telah menulis artikel tentang cara mengimplementasikannya di Python, lihat di sini.
Relatif mudah menjadi daemon di Java.
Thread t = new daemont ();
t.setdaemon (true);
t.start ();
Ini membuka benang daemon.
tes paket; tes kelas publik {public static class Daemanthread memperluas utas {@Override public void run () {for (int i = 0; i <10000000; i ++) {System.out.println ("hai"); }}} public static void main (string [] args) melempar interruptedException {daememonthread dt = new Daemanthread (); dt.start (); }} Saat utas DT bukan utas daemon, setelah berjalan, kita dapat melihat output konsol HI
Saat bergabung sebelum mulai
dt.setdaemon (true);
Konsol keluar secara langsung dan tidak memiliki output.
4. Prioritas utas
Ada 3 variabel di kelas utas yang menentukan prioritas utas.
public final static int min_priority = 1; public final static int norm_priority = 5; public final static int max_priority = 10; test paket; tes kelas publik {public static class tinggi memperluas utas {static int count = 0; @Override public void run () {while (true) {disinkronkan (test.class) {count ++; if (count> 10000000) {System.out.println ("High"); merusak; }}}}} kelas statis public low extends thread {static int count = 0; @Override public void run () {while (true) {disinkronkan (test.class) {count ++; if (count> 10000000) {System.out.println ("Low"); merusak; }}}}} public static void main (string [] args) melempar interruptedException {high high = new high (); Rendah rendah = rendah baru (); high.setPriority (thread.max_priority); low.setpriority (thread.min_priority); low.start (); high.start (); }} Biarkan utas prioritas tinggi dan utas prioritas rendah bersaing untuk mengunci pada saat yang sama dan lihat mana yang selesai terlebih dahulu.
Tentu saja, itu tidak harus diselesaikan terlebih dahulu dengan prioritas tinggi. Setelah menjalankannya beberapa kali, saya menemukan bahwa probabilitas penyelesaian prioritas tinggi relatif tinggi, tetapi masih mungkin untuk menyelesaikannya terlebih dahulu dengan prioritas rendah.
5. Operasi sinkronisasi utas dasar
disinkronkan dan objek.wait () obejct.notify ()
Untuk perincian bagian ini, silakan merujuk ke blog yang saya tulis sebelumnya.
Hal utama yang perlu diperhatikan adalah
Ada tiga cara untuk mengunci disinkronkan:
Tentukan Objek Terkunci: Kunci objek yang diberikan dan dapatkan kunci objek yang diberikan sebelum memasukkan kode sinkronisasi.
Langsung bertindak pada metode instan: setara dengan mengunci instance saat ini. Sebelum memasukkan kode sinkronisasi, Anda harus mendapatkan kunci instance saat ini.
Bertindak langsung pada metode statis: setara dengan mengunci kelas saat ini. Sebelum memasukkan kode sinkron, Anda harus mendapatkan kunci kelas saat ini.
Jika bekerja pada metode instan, jangan baru dua contoh yang berbeda
Ini bekerja pada metode statis, selama kelasnya sama, karena kunci yang ditambahkan adalah kelas. Kelas, dan dua contoh yang berbeda bisa menjadi baru.
Cara menggunakan tunggu dan memberi tahu:
Gunakan kunci apa pun, hubungi tunggu dan beri tahu
Saya tidak akan membahas detail dalam artikel ini.