Daftar isi(?)[-]
Satu memperluas JavalangThread kelas dua mengimplementasikan antarmuka javalangrunnable tiga perbedaan antara utas dan runnable empat thread state transition lima penjadwalan utas enam fungsi umum yang menjelaskan cara menggunakan mengapa metode gabung digunakan tujuh nomasah nominasi umum penjelasan delapan utas sinkronisasi sembilan transmisi data utas utas utas utas transmisi utas utas utas transmisi utas utas utas transmisi utas utas digunakan digunakan transmisi utas utas utas.
Artikel ini terutama berbicara tentang metode penggunaan multi-threading di Java, sinkronisasi utas, transfer data utas, status utas dan penggunaan fungsi utas yang sesuai dan gambaran umum.
Pertama, mari kita bicara tentang perbedaan antara suatu proses dan utas:
Proses: Setiap proses memiliki kode independen dan ruang data (konteks proses), dan beralih antar proses akan memiliki overhead yang besar. Suatu proses berisi utas 1-N.
Thread: Jenis utas yang sama berbagi kode dan ruang data. Setiap utas memiliki Stack dan Program Counter (PC) independen (PC), dan overhead switching utasnya kecil.
Seperti suatu proses, utas dibagi menjadi lima tahap: pembuatan, siap, berjalan, memblokir, dan berakhir.
Multi-proses berarti bahwa sistem operasi dapat menjalankan banyak tugas (program) secara bersamaan.
Multithreading mengacu pada beberapa aliran berurutan yang dieksekusi dalam program yang sama.
Di Java, ada dua cara untuk mengimplementasikan multi-threading. Salah satunya adalah melanjutkan kelas utas, dan yang lainnya adalah mengimplementasikan antarmuka yang dapat dijalankan.
1. Perpanjang kelas java.lang.thread
Paket com.multithread.learning;/***@functon multithreading learning*@penulis lin Bingwen*@time 2015.3.9*/class thread1 memperluas thread {name string privat; thread publik1 (nama string) {this.name = name; } public void run () {for (int i = 0; i <5; i ++) {System.out.println (name + "run:" + i); coba {sleep ((int) math.random () * 10); } catch (InterruptedException e) {E.PrintStackTrace (); }}}} kelas publik main {public static void main (string [] args) {thread1 mth1 = new thread1 ("a"); Thread1 mth2 = new thread1 ("b"); mth1.start (); mth2.start (); }} Keluaran:
Lari: 0
B Jalankan: 0
A Run: 1
Lari: 2
Lari: 3
Lari: 4
B Jalankan: 1
B Run: 2
B Run: 3
B Run: 4
Jalankan lagi:
Lari: 0
B Jalankan: 0
B Jalankan: 1
B Run: 2
B Run: 3
B Run: 4
A Run: 1
Lari: 2
Lari: 3
Lari: 4
menjelaskan:
Ketika program dimulai dan menjalankan utama, mesin virtual Java memulai proses, dan utas utama dibuat ketika utama () dipanggil. Dengan metode awal dari dua objek Mitisay, dua utas lainnya juga dimulai, sehingga seluruh aplikasi berjalan di bawah beberapa utas.
CATATAN: Metode Start () dipanggil untuk tidak segera menjalankan kode multi-threaded segera, tetapi sebaliknya membuat utas menjadi keadaan yang dapat dijalankan. Ketika dijalankan ditentukan oleh sistem operasi.
Dari hasil program yang berjalan, kami dapat menemukan bahwa program multi-utas dieksekusi dari urutan. Oleh karena itu, hanya kode yang dieksekusi di luar pesanan yang harus dirancang sebagai multi-threaded.
Tujuan dari doa metode utas.
Faktanya, urutan eksekusi semua kode multi-threaded tidak pasti, dan hasil dari setiap eksekusi acak.
Namun, jika metode awal disebut berulang kali, java.lang.illegalthreadStateException akan terjadi.
Thread1 mth1 = new thread1 ("a"); Thread1 mth2 = mth1; mth1.start (); mth2.start (); Keluaran:
Pengecualian di Thread "Main" Java.lang.illegalthreadStateException
di java.lang.thread.start (sumber yang tidak diketahui)
di com.multithread.learning.main.main (main.java:31)
Lari: 0
A Run: 1
Lari: 2
Lari: 3
Lari: 4
2. Menerapkan antarmuka java.lang.runnable
/***@functon Multithreading Learning*@penulis lin Bingwen*@time 2015.3.9*/package com.multithread.runnable; kelas thread2 mengimplementasikan runnable {private string name; thread publik2 (nama string) {this.name = name; } @Override public void run () {for (int i = 0; i <5; i ++) {System.out.println (name + "run:" + i); coba {thread.sleep ((int) math.random () * 10); } catch (InterruptedException e) {E.PrintStackTrace (); }}}} kelas publik utama {public static void main (string [] args) {thread baru (thread baru2 ("c")). start (); utas baru (utas baru2 ("d")). start (); }} Keluaran:
C Jalankan: 0
D Run: 0
D Run: 1
C Run: 1
D Run: 2
C Run: 2
D Run: 3
C Run: 3
D Run: 4
C Run: 4
menjelaskan:
Kelas Thread2 mengimplementasikan antarmuka yang dapat dijalankan, yang membuat kelas memiliki karakteristik kelas multi-threaded. Metode run () adalah konvensi untuk program multithreaded. Semua kode multi-threaded ada dalam metode run. Kelas utas sebenarnya adalah kelas yang mengimplementasikan antarmuka yang dapat dijalankan.
Saat memulai multi-threading, Anda harus terlebih dahulu membangun objek melalui utas konstruktor kelas utas (target runnable), dan kemudian panggil metode start () dari objek utas untuk menjalankan kode multi-thread.
Faktanya, semua kode multithreaded dijalankan dengan menjalankan metode start thread (). Oleh karena itu, apakah itu untuk memperluas kelas utas atau mengimplementasikan antarmuka yang dapat dilalui untuk mengimplementasikan multi-threading, atau pada akhirnya mengontrol utas melalui API objek utas, membiasakan diri dengan API kelas utas adalah dasar untuk pemrograman multi-thread.
3. Perbedaan antara utas dan runnable
Jika kelas mewarisi utas, itu tidak cocok untuk berbagi sumber daya. Namun, jika antarmuka yang dapat dijalankan diimplementasikan, mudah untuk menerapkan berbagi sumber daya.
Paket com.multithread.learning;/***@functon multi-thread learning, utas waris, sumber daya tidak dapat dibagikan*@penulis lin Bingwen*@time 2015.3.9*/class thread1 memperluas utas {private int count = 5; nama string pribadi; thread publik1 (nama string) {this.name = name; } public void run () {for (int i = 0; i <5; i ++) {System.out.println (name + "run count =" + count--); coba {sleep ((int) math.random () * 10); } catch (InterruptedException e) {E.PrintStackTrace (); }}}} kelas publik main {public static void main (string [] args) {thread1 mth1 = new thread1 ("a"); Thread1 mth2 = new thread1 ("b"); mth1.start (); mth2.start (); }} Keluaran:
B run count = 5
Jumlah run = 5
B run count = 4
B run count = 3
B run count = 2
B run count = 1
Jumlah lari = 4
Jumlah lari = 3
Jumlah run = 2
Jumlah run = 1
Dari yang di atas, kita dapat melihat bahwa jumlahnya berbeda antara utas yang berbeda, yang akan memiliki masalah besar untuk sistem penjualan tiket. Tentu saja, sinkronisasi dapat digunakan di sini. Mari kita gunakan runnable untuk melakukannya di sini
/***@Functon Multi-Threading Learning mewarisi runnable, sumber daya dapat dibagikan*@penulis lin Bingwen*@time 2015.3.9*/package com.multithread.runnable; kelas thread2 mengimplementasikan runnable {private int count = 15; @Override public void run () {for (int i = 0; i <5; i ++) {System.out.println (thread.currentThread (). GetName () + "run count =" + count--); coba {thread.sleep ((int) math.random () * 10); } catch (InterruptedException e) {E.PrintStackTrace (); }}}}} kelas publik utama {public static void main (string [] args) {thread2 my = new thread2 (); utas baru (my, "c"). start (); // mt yang sama, tetapi tidak mungkin di utas. Jika Anda instantiate objek MT, pengecualian akan muncul utas baru (my, "d"). Start (); utas baru (my, "e"). start (); }} Keluaran:
C run count = 15
D run count = 14
E run count = 13
D Jalankan Count = 12
D run count = 10
D run count = 9
D run count = 8
C run count = 11
E run count = 12
C run count = 7
E run count = 6
C run count = 5
E run count = 4
C run count = 3
E run count = 2
Di sini kita harus mencatat bahwa setiap utas menggunakan objek instantiasi yang sama. Jika tidak sama, efeknya akan sama seperti di atas!
Meringkaskan:
Keuntungan menerapkan antarmuka runnable dibandingkan mewarisi kelas utas:
1): Cocok untuk beberapa utas dengan kode program yang sama untuk memproses sumber daya yang sama
2): dapat menghindari batasan warisan tunggal di java
3): Tingkatkan ketahanan program, kode dapat dibagikan oleh banyak utas, dan kode dan data independen
Izinkan saya mengingatkan Anda: Metode utama sebenarnya adalah utas. Di Java, utas dimulai pada saat yang sama. Adapun kapan dan mana yang dieksekusi terlebih dahulu, itu sepenuhnya tergantung pada siapa yang mendapatkan sumber daya CPU terlebih dahulu.
Di Java, setidaknya 2 utas dimulai setiap kali program berjalan. Salah satunya adalah utas utama dan yang lainnya adalah utas pengumpulan sampah. Karena setiap kali kelas dieksekusi menggunakan perintah Java, JVM benar -benar akan dimulai, dan setiap magang JVM memulai proses dalam sistem operasi.
4. Transisi keadaan utas
1. Status Baru (Baru): Objek utas baru dibuat.
2. Siap Status (Runnable): Setelah objek utas dibuat, utas lain memanggil metode start () dari objek. Utas dalam keadaan ini terletak di kumpulan utas yang dapat dilalui dan menjadi dapat dikeluarkan, menunggu untuk mendapatkan hak penggunaan CPU.
3. Running State: Thread dalam keadaan siap memperoleh CPU dan menjalankan kode program.
4. Status yang diblokir: Keadaan yang diblokir berarti bahwa utas tersebut menyerahkan hak penggunaan CPU untuk beberapa alasan dan sementara berhenti berjalan. Tidak sampai utas memasuki keadaan siap, ia memiliki kesempatan untuk pergi ke keadaan berjalan. Ada tiga jenis penyumbatan:
(1) Menunggu untuk memblokir: Benang yang berjalan mengeksekusi metode tunggu (), dan JVM akan memasukkan utas ke dalam kolam penunggu.
(2) Pemblokiran sinkron: Ketika utas yang berjalan memperoleh kunci sinkronisasi objek, jika kunci sinkronisasi ditempati oleh utas lain, JVM akan memasukkan utas ke dalam kumpulan kunci.
(Iii), pemblokiran lainnya: Ketika utas yang berjalan mengeksekusi metode tidur () atau gabung (), atau mengeluarkan permintaan I/O, JVM akan mengatur utas ke status pemblokiran. Ketika status tidur () diatur waktu, bergabung () menunggu utas untuk diakhiri atau diatur waktunya, atau pemrosesan I/O selesai, utas yang dimasukkan kembali ke keadaan siap.
5. Status Mati: Utas telah selesai mengeksekusi atau keluar dari metode run () karena pengecualian, dan utas mengakhiri siklus hidupnya.
5. Penjadwalan Thread
Penjadwalan utas
1. Sesuaikan Prioritas Thread: Benang Java memiliki prioritas, dan utas dengan prioritas tinggi akan mendapatkan lebih banyak peluang untuk dijalankan.
Prioritas utas Java diwakili oleh bilangan bulat, dengan kisaran nilai 1 ~ 10. Kelas utas memiliki tiga konstanta statis berikut:
statis int max_priority
Prioritas tertinggi yang dapat dimiliki oleh utas adalah 10.
static int min_priority
Prioritas terendah yang dapat dimiliki oleh utas adalah 1.
statis int norm_priority
Prioritas default yang ditetapkan ke utas adalah 5.
Metode setPriority () dan getPriority () dari kelas utas digunakan untuk mengatur dan mendapatkan prioritas utas masing -masing.
Setiap utas memiliki prioritas default. Prioritas default utas utama adalah thread.norm_priority.
Prioritas utas diwarisi. Misalnya, jika utas B dibuat di utas A, maka B akan memiliki prioritas yang sama dengan A.
JVM memberikan 10 prioritas utas, tetapi tidak memetakan dengan baik dengan sistem operasi umum. Jika Anda ingin program porting ke setiap sistem operasi, Anda hanya boleh menggunakan kelas utas dengan tiga konstanta statis berikut sebagai prioritas, yang dapat memastikan bahwa prioritas yang sama mengadopsi metode penjadwalan yang sama.
2. Thread Sleep: Metode Thread.sleep (Long Millis) untuk membuat utas pergi ke status pemblokiran. Parameter Millis menetapkan waktu tidur dalam milidetik. Saat tidur selesai, itu menjadi dapat dijalankan. Platform tidur () memiliki portabilitas yang baik.
3. Thread tunggu: Metode tunggu () di kelas objek menyebabkan utas saat ini menunggu sampai utas lain memanggil metode pemberitahuan objek () atau metode bangun-up. Dua metode bangun ini juga merupakan metode di kelas objek, dan perilakunya setara dengan panggilan Wait (0).
4. Konsesi Thread: Metode thread.yield () menangguhkan objek utas yang sedang dieksekusi dan memberikan peluang eksekusi untuk utas dengan prioritas yang sama atau lebih tinggi.
5. Thread gabungan: gabungan () Metode, menunggu utas lain untuk diakhiri. Memanggil metode join () dari utas lain di utas saat ini, utas saat ini masuk ke keadaan pemblokiran sampai proses lainnya berjalan, dan utas saat ini beralih dari pemblokiran ke status siap.
6. Bangun utas: Metode notify () di kelas objek membangunkan satu utas yang menunggu pada monitor objek ini. Jika semua utas menunggu pada objek ini, salah satu utas akan dipilih. Pilihannya sewenang -wenang dan terjadi ketika membuat keputusan tentang implementasi. Utas menunggu monitor objek dengan memanggil salah satu metode tunggu. Utas bangun tidak dapat dieksekusi sampai utas saat ini meninggalkan kunci pada objek ini. Utas bangun akan bersaing dengan semua utas lain yang secara aktif disinkronkan pada objek dengan cara konvensional; Misalnya, utas bangun tidak memiliki hak istimewa atau kerugian yang dapat diandalkan menjadi utas berikutnya yang mengunci objek ini. Metode serupa juga memiliki notifyall () yang membangunkan semua utas yang menunggu pada monitor objek ini.
CATATAN: Dua metode suspend () dan resume () di utas telah dihapuskan di JDK1.5 dan tidak akan diperkenalkan lagi. Karena ada kecenderungan untuk menemui jalan buntu.
6. Deskripsi Fungsi Umum
① Sleep (Long Millis): Biarkan utas yang saat ini dieksekusi tidur dalam jumlah milidetik yang ditentukan (eksekusi penangguhan)
②join (): mengacu pada menunggu utas T untuk berakhir.
Bagaimana menggunakannya.
Bergabung adalah metode kelas utas. Disebut langsung setelah memulai utas. Artinya, fungsi gabungan () adalah: "Tunggu utas untuk diakhiri". Apa yang perlu dipahami di sini adalah bahwa utas mengacu pada utas utama menunggu utas anak untuk berakhir. Artinya, kode setelah utas anak memanggil metode gabungan (), dan hanya dapat dieksekusi sampai utas anak selesai.
Thread t = new athread (); t.start (); t.join ();
Mengapa menggunakan metode gabungan ()
Dalam banyak kasus, utas utama menghasilkan dan memulai utas anak. Jika sejumlah besar operasi yang memakan waktu diperlukan di utas anak, utas utama akan sering berakhir sebelum utas anak. Namun, jika utas utama perlu menggunakan hasil pemrosesan utas anak setelah pemrosesan transaksi lainnya, yaitu, utas utama perlu menunggu utas anak untuk menyelesaikan eksekusi sebelum berakhir. Pada saat ini, metode gabungan () harus digunakan.
Tidak bergabung. /** *@functon multithreading learning, gabung *@penulis lin bingwen *@time 2015.3.9 */package com.multithread.join; class thread1 memperluas utas {name string pribadi; thread publik1 (nama string) {super (name); this.name = name; } public void run () {System.out.println (thread.currentThread (). getName () + "Thread dimulai!"); untuk (int i = 0; i <5; i ++) {System.out.println ("subthread"+name+"run:"+i); coba {sleep ((int) math.random () * 10); } catch (InterruptedException e) {E.PrintStackTrace (); }} System.out.println (thread.currentThread (). GetName () + "Thread run end!"); }} kelas publik utama {public static void main (string [] args) {system.out.println (thread.currentThread (). getName ()+"Utas utama run start!"); Thread1 mth1 = new thread1 ("a"); Thread1 mth2 = new thread1 ("b"); mth1.start (); mth2.start (); System.out.println (thread.currentThread (). GetName ()+ "Ujung utas utama berakhir!"); }} Hasil output:
Utas utama mulai berjalan!
Ujung utas utama utama berjalan!
B Lari utas dimulai!
Utas anak B berjalan: 0
Lari utas dimulai!
Utas anak beroperasi: 0
Utas anak B berjalan: 1
Utas anak beroperasi: 1
Utas anak beroperasi: 2
Utas anak beroperasi: 3
Utas anak beroperasi: 4
Benang yang dijalankan berakhir!
Utas anak B berjalan: 2
Utas anak B berjalan: 3
Utas anak B berjalan: 4
B Thread mengalir!
Menemukan bahwa utas utama berakhir lebih awal dari utas anak
Bergabung
kelas publik {public static void main (string [] args) {System.out.println (thread.currentThread (). getName ()+"Utas utama run start!"); Thread1 mth1 = new thread1 ("a"); Thread1 mth2 = new thread1 ("b"); mth1.start (); mth2.start (); coba {mth1.join (); } catch (InterruptedException e) {E.PrintStackTrace (); } coba {mth2.join (); } catch (InterruptedException e) {E.PrintStackTrace (); } System.out.println (thread.currentThread (). GetName ()+ "Ujung utas utama berakhir!"); }} Hasil Menjalankan:
Utas utama mulai berjalan!
Lari utas dimulai!
Utas anak beroperasi: 0
B Lari utas dimulai!
Utas anak B berjalan: 0
Utas anak beroperasi: 1
Utas anak B berjalan: 1
Utas anak beroperasi: 2
Utas anak B berjalan: 2
Utas anak beroperasi: 3
Utas anak B berjalan: 3
Utas anak beroperasi: 4
Utas anak B berjalan: 4
Benang yang dijalankan berakhir!
Utas utama pasti akan menunggu sampai utas anak selesai sebelum berakhir.
③YIELD (): Menjeda objek utas yang saat ini dieksekusi dan menjalankan utas lainnya.
Fungsi metode thread.yield () adalah: jeda objek utas yang sedang dieksekusi dan jalankan utas lainnya.
Apa yang harus dilakukan () adalah untuk mendapatkan utas berjalan saat ini kembali ke status runnable untuk memungkinkan utas lain dengan prioritas yang sama untuk mendapatkan peluang lari. Oleh karena itu, tujuan menggunakan hasil () adalah untuk memungkinkan utas dari prioritas yang sama untuk melakukan dengan tepat. Namun, pada kenyataannya, hasil () tidak dapat dijamin untuk mencapai tujuan konsesi, karena utas konsesi dapat dipilih lagi oleh penjadwal utas.
Kesimpulan: Hasil () tidak pernah menyebabkan utas pergi ke keadaan menunggu/tidur/pemblokiran. Dalam kebanyakan kasus, Yield () akan menyebabkan utas beralih dari keadaan berjalan, tetapi mungkin tidak berfungsi. Anda dapat melihat gambar di atas.
/** *@functon multithreading learning yield *@penulis lin bingwen *@time 2015.3.9 */package com.multithread.yield; kelas threadyield memperluas utas {public threadyield (nama string) {super (nama); } @Override public void run () {for (int i = 1; i <= 50; i ++) {System.out.println ("" + this.getName () + "-----" + i); // Ketika saya berusia 30 tahun, utas akan menyerahkan waktu CPU dan membiarkan utas lain atau utasnya sendiri dieksekusi (yaitu, siapa pun yang mengambilnya terlebih dahulu menggunakannya) jika (i == 30) {this.yield (); }}}} kelas publik main {public static void main (string [] args) {threadyield yt1 = new threadyield ("zhang san"); Threadyield yt2 = new threadyield ("li si"); yt1.start (); yt2.start (); }} Hasil Menjalankan:
Kasus pertama: Li Si (Thread) akan mendapatkan waktu CPU ketika dieksekusi hingga 30. Pada saat ini, Zhang San (Thread) meraih waktu CPU dan mengeksekusinya.
Situasi kedua: Ketika li si (utas) dijalankan ke 30, waktu CPU akan ditinggalkan. Pada saat ini, Li Si (Thread) meraih waktu CPU dan menjalankannya.
Perbedaan antara tidur () dan hasil ()
Perbedaan antara tidur () dan hasil ()): sleep () menyebabkan utas saat ini memasuki keadaan stagnan, sehingga utas yang mengeksekusi tidur () pasti tidak akan dieksekusi dalam waktu yang ditentukan; hasil () hanya menyebabkan utas saat ini untuk kembali ke negara yang dapat dieksekusi, sehingga hasil pelaksanaan utas () dapat dieksekusi segera setelah memasuki negara yang dapat dieksekusi.
Metode tidur menyebabkan benang yang sedang berjalan saat ini tidur untuk jangka waktu tertentu dan memasuki keadaan yang tidak dapat dilewati. Panjang periode ini ditetapkan oleh program. Metode hasil memungkinkan utas saat ini untuk melepaskan kepemilikan CPU, tetapi waktu transfer tidak dapat diselesaikan. Bahkan, metode hasil () sesuai dengan operasi berikut: Pertama periksa apakah ada utas dengan prioritas yang sama saat ini dalam keadaan runnable yang sama. Jika demikian, serahkan kepemilikan CPU ke utas ini, jika tidak, terus jalankan utas asli. Jadi metode hasil () disebut "konsesi", yang memberikan peluang lari ke utas lain dengan prioritas yang sama
Selain itu, metode tidur memungkinkan utas prioritas yang lebih rendah untuk mendapatkan peluang yang dijalankan, tetapi ketika metode hasil () dieksekusi, utas saat ini masih dalam keadaan runnable, sehingga tidak mungkin untuk melepaskan utas prioritas yang lebih rendah untuk mendapatkan kepemilikan CPU nanti. Dalam sistem yang sedang berjalan, jika utas prioritas yang lebih tinggi tidak memanggil metode tidur dan tidak diblokir oleh I/O, maka utas prioritas yang lebih rendah hanya dapat menunggu semua utas prioritas yang lebih tinggi berjalan untuk memiliki kesempatan untuk berjalan.
④SetPriority (): Ubah prioritas utas.
Min_priority = 1
Norm_priority = 5
MAX_PRIORITY = 10
penggunaan:
Thread4 t1 = thread4 baru ("t1");
Thread4 t2 = thread4 baru ("t2");
t1.setPriority (thread.max_priority);
t2.setPriority (thread.min_priority);
⑤IntRupt (): mengganggu utas. Metode akhir ini agak kasar. Jika utas T membuka sumber daya dan belum punya waktu untuk menutupnya, yaitu, metode RUN dipaksa untuk mengakhiri utas sebelum dieksekusi, yang akan menyebabkan sumber daya gagal ditutup.
Cara terbaik untuk mengakhiri proses adalah dengan menggunakan contoh program fungsi tidur (). Variabel boolean digunakan di kelas utas untuk mengontrol ketika metode run () berakhir. Setelah metode run () berakhir, utas berakhir.
⑥Wait ()
Obj.wait () dan obj.notify () harus digunakan dengan disinkronkan (OBJ), yaitu, tunggu, dan beri tahu beroperasi pada kunci OBJ yang telah diperoleh. Dari sudut pandang yang disinkronkan, itu adalah obj.wait (), dan obj.notify harus dalam blok pernyataan yang disinkronkan (obj) {...}. Dari perspektif fungsional, tunggu berarti bahwa setelah utas memperoleh kunci objek, ia secara aktif melepaskan kunci objek, dan utas tidur. Kunci objek tidak dapat diperoleh dan eksekusi akan berlanjut sampai utas lain memanggil objek memberi tahu () untuk membangunkan utas. Notify yang sesuai () adalah operasi bangun dari kunci objek. Tetapi satu hal yang perlu diperhatikan adalah bahwa setelah panggilan notify (), kunci objek tidak segera dirilis, tetapi eksekusi blok pernyataan yang disinkronkan () {} selesai dan kunci secara otomatis dilepaskan, JVM secara acak akan memilih utas dari utas kunci tunggu (), tentukan ke kunci objek, bangunkan utas, dan lanjutkan eksekusi. Ini memberikan sinkronisasi dan operasi bangun antara utas. Baik thread.sleep () dan object.Wait () dapat menjeda utas saat ini dan melepaskan kontrol CPU. Perbedaan utama adalah bahwa sementara objek.Wait () melepaskan CPU, ia melepaskan kontrol kunci objek.
Tidaklah cukup untuk memahami secara konseptual, dan perlu diuji dalam contoh -contoh praktis untuk lebih memahami. Contoh paling klasik dari aplikasi objek.Wait () dan objek.notify () harus menjadi masalah pencetakan ABC dengan tiga utas. Ini adalah pertanyaan wawancara yang relatif klasik, dan pertanyaannya adalah sebagai berikut:
Buat tiga utas, benang A cetakan A 10 kali, benang B mencetak B 10 kali, benang C mencetak C 10 kali, Thread C membutuhkan utas untuk dijalankan pada saat yang sama, dan ABC dicetak secara bergantian 10 kali. Masalah ini dapat dengan mudah diselesaikan dengan menggunakan Object's Wait () dan Notify (). Kodenya adalah sebagai berikut:
/** * Tunggu penggunaan * @Author Dreamsea * @Time 2015.3.9 */package com.multithread.wait; kelas publik myThreadPrinter2 mengimplementasikan runnable {private string name; Prev Private Object Prev; diri objek pribadi; private myThreadPrinter2 (nama string, objek prev, objek self) {this.name = name; this.prev = prev; this.self = self; } @Override public void run () {int count = 10; while (count> 0) {disinkronkan (prev) {disinkronkan (self) {System.out.print (name); menghitung--; self.notify (); } coba {prev.wait (); } catch (InterruptedException e) {E.PrintStackTrace (); }}}} public static void main (string [] args) melempar Exception {objek A = objek baru (); Objek B = objek baru (); Objek c = objek baru (); MyThreadPrinter2 pa = myThreadPrinter2 baru ("a", c, a); MyThreadPrinter2 pb = myThreadPrinter2 baru ("b", a, b); MyThreadPrinter2 pc = myThreadPrinter2 baru ("c", b, c); utas baru (pa) .start (); Thread.sleep (100); // Pastikan untuk mengeksekusi utas baru (PB) .start (); Thread.sleep (100); }} Hasil output:
ABCABCABCABCABCABCABCABCABCABCABC
Pertama -tama mari kita jelaskan ide keseluruhannya. Dari perspektif umum, masalah ini adalah operasi bangun yang sinkron antara tiga utas. Tujuan utamanya adalah untuk menjalankan tiga utas di threada-> threadb-> threadc-> threada loop. Untuk mengontrol urutan eksekusi utas, urutan bangun dan tunggu harus ditentukan, sehingga setiap utas harus menampung dua kunci objek pada waktu yang sama sebelum dapat melanjutkan eksekusi. Kunci objek adalah prev, yang merupakan kunci objek yang dipegang oleh utas sebelumnya. Yang lain adalah kunci objek. Gagasan utamanya adalah bahwa untuk mengontrol urutan eksekusi, Anda harus terlebih dahulu memegang kunci PREV, yaitu, utas sebelumnya harus melepaskan kunci objeknya sendiri, dan kemudian berlaku untuk kunci objeknya sendiri. Cetak saat keduanya. Kemudian, panggilan pertama self.notify () untuk melepaskan kunci objeknya sendiri, bangunkan utas tunggu berikutnya, dan kemudian hubungi prev.wait () untuk melepaskan kunci objek PREV, hentikan utas saat ini, dan tunggu loop dibangunkan lagi. Jalankan kode di atas dan Anda dapat menemukan bahwa tiga utas mencetak ABC dalam satu loop, total 10 kali. The main process of program running is that thread A is the first to run, holds the object locks of C and A, and then releases the locks of A and C, and wakes up B. Thread B waits for lock A, then applies for lock B, then prints B, then releases B, A lock, wakes C, thread C waits for lock B, then applies for lock C, then prints C, then releases C, B lock, and wakes A. It seems that there is no Masalah, tetapi jika Anda memikirkannya dengan cermat, Anda akan menemukan bahwa ada masalah, yang merupakan kondisi awal. Tiga utas dimulai dalam urutan A, B, dan C. Menurut pemikiran sebelumnya, A membangunkan B, B bangun C, C dan kemudian bangun A. Namun, asumsi ini tergantung pada urutan penjadwalan dan pelaksanaan utas di JVM.
Perbedaan antara menunggu dan tidur
Poin Umum:
1. Mereka semua berada di lingkungan multi-utas, dan dapat memblokir jumlah milidetik yang ditentukan pada panggilan dan pengembalian program.
2. Baik tunggu () dan sleep () dapat mengganggu status jeda utas melalui metode interrupt (), sehingga utas segera melempar Exception interrupted.
Jika utas A ingin mengakhiri utas B segera, metode interupsi dapat dipanggil pada instance utas yang sesuai dengan utas B. Jika utas B sedang menunggu/tidur/bergabung pada saat ini, Thread B akan segera melempar Exception interrupted, dan mengembalikannya langsung di catch () {} untuk mengakhiri utas dengan aman.
Perlu dicatat bahwa ExterruptException dilemparkan oleh utas itu sendiri dari dalam, bukan dengan metode interrupt (). Ketika interrupt () dipanggil pada utas, jika utas menjalankan kode normal, utas tidak akan melempar Exception interrupted sama sekali. Namun, begitu utas masuk tunggu ()/sleep ()/join (), interruptedException akan segera dilemparkan.
Perbedaan:
1. Metode kelas utas: tidur (), hasil (), dll.
Metode objek: tunggu () dan beri tahu (), dll.
2. Setiap objek memiliki kunci untuk mengontrol akses sinkron. Kata kunci yang disinkronkan dapat berinteraksi dengan kunci objek untuk mewujudkan sinkronisasi utas.
Metode tidur tidak melepaskan kunci, sementara metode tunggu melepaskan kunci, sehingga utas lain dapat menggunakan blok atau metode kontrol sinkron.
3. Tunggu, beri tahu dan beri tahu semua hanya dapat digunakan dalam metode kontrol sinkronisasi atau blok kontrol sinkronisasi, sementara tidur dapat digunakan di mana saja.
4. Tidur harus menangkap pengecualian, sambil menunggu, memberi tahu dan memberi tahu semua tidak perlu menangkap pengecualian, jadi perbedaan terbesar antara tidur () dan metode tunggu () adalah:
Saat tidur () tidur, jaga agar objek mengunci dan masih memiliki kunci;
Saat menunggu () tidur, kunci objek dilepaskan.
Namun, tunggu () dan sleep () keduanya dapat mengganggu status jeda utas melalui metode interrupt (), sehingga utas segera melempar interruptedException (tetapi tidak disarankan untuk menggunakan metode ini).
metode tidur ()
sleep () menyebabkan utas saat ini memasuki keadaan stagnan (memblokir utas saat ini), menyerahkan penggunaan cangkir, dan tujuannya adalah untuk mencegah utas saat ini menempati sumber daya CPU yang diperoleh dengan proses saja, sehingga meninggalkan waktu tertentu bagi utas lain untuk dieksekusi;
sleep () adalah metode statis dari kelas utas; Oleh karena itu tidak dapat mengubah kunci mesin objek, jadi ketika memanggil metode tidur () dalam blok yang disinkronkan, meskipun utasnya tidak aktif, kunci mesin objek tidak dilepaskan, dan utas lainnya tidak dapat mengakses objek (meskipun masih memegang kunci objek bahkan ketika tertidur).
Setelah tidur () tidur () waktu tidur berakhir, utas tidak harus segera dieksekusi, karena utas lain mungkin berjalan dan tidak dijadwalkan untuk meninggalkan eksekusi kecuali utas memiliki prioritas yang lebih tinggi.
tunggu () metode
Metode tunggu () adalah metode di kelas objek; Ketika utas mengeksekusi metode tunggu (), ia memasuki kolam tunggu yang terkait dengan objek, dan pada saat yang sama kehilangan (melepaskan) kunci mesin objek (sementara kehilangan kunci mesin, dan waktu tunggu tunggu (waktu lama) juga perlu mengembalikan kunci objek); Utas lain dapat mengaksesnya;
tunggu () menggunakan notify atau notifyall atau waktu tidur yang ditentukan untuk membangunkan utas di kolam tunggu saat ini.
Wiat () harus ditempatkan di blok yang disinkronkan, jika tidak, "Java.lang.illegalmonitorStateException" akan dilemparkan saat runtime program.
7. Penjelasan istilah utas umum
Utas utama: Utas yang dihasilkan oleh program panggilan JVM Main ().
Utas saat ini: Ini adalah konsep yang membingungkan. Umumnya mengacu pada proses yang diperoleh melalui thread.currentThread ().
Utas latar belakang: Mengacu pada utas yang menyediakan layanan ke utas lain, juga dikenal sebagai utas daemon. Utas pengumpulan sampah JVM adalah utas latar belakang. Perbedaan antara utas pengguna dan utas daemon adalah apakah akan menunggu utas utama untuk mengakhiri utas latar depan tergantung pada ujung utas utama: itu mengacu pada utas yang menerima layanan utas latar belakang. Faktanya, utas latar belakang latar depan terhubung bersama, seperti hubungan antara boneka dan manipulator di belakang layar. Boneka adalah utas latar depan, dan manipulator di belakang layar adalah utas latar belakang. Utas yang dibuat oleh utas latar depan juga merupakan utas latar depan secara default. Anda dapat menggunakan metode isdaemon () dan setdaemon () untuk menentukan dan mengatur apakah utas adalah utas latar belakang.
Beberapa metode umum kelas utas:
sleep (): Paksa benang untuk tidur dalam N milidetik.
IsAlive (): Menentukan apakah utas bertahan.
gabung (): Tunggu utas untuk diakhiri.
ActiveCount (): Jumlah utas aktif dalam program.
Menipis (): Menghitung utas dalam program.
currentThread (): Mendapat utas saat ini.
isdaemon (): Apakah utas adalah utas daemon.
setdaemon (): Atur utas sebagai benang daemon. (Perbedaan antara utas pengguna dan utas daemon adalah apakah akan menunggu utas utama berakhir tergantung pada ujung utas utama)
setName (): Atur nama untuk utas.
tunggu (): Paksa utas untuk menunggu.
Notify (): Beri tahu utas untuk terus berjalan.
setPriority (): Mengatur prioritas utas.
8. Sinkronisasi Thread
1. Ada dua lingkup kata kunci yang disinkronkan:
1) Ini dalam instance objek. Amethod yang disinkronkan () {} dapat mencegah beberapa utas dari mengakses metode yang disinkronkan dari objek ini pada saat yang sama (jika suatu objek memiliki beberapa metode yang disinkronkan, selama satu utas mengakses salah satu metode yang disinkronkan, utas lain tidak dapat mengakses metode yang disinkronkan dalam objek pada saat yang sama). Pada saat ini, metode sinkronisasi dari berbagai contoh objek tidak terputus. That is to say, other threads can still access the synchronized method in another object instance of the same class at the same time;
2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象;
3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;
Java对多线程的支持与同步机制深受大家的喜爱,似乎看起来使用了synchronized关键字就可以轻松地解决多线程共享数据同步问题。到底如何?还得对synchronized关键字的作用进行深入了解才可定论。
总的说来,synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。
在进一步阐述之前,我们需要明确几点:
A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁而且同步方法很可能还会被其他线程的对象访问。
B.每个对象只有一个锁(lock)与之相关联。
C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
接着来讨论synchronized用到不同地方对代码产生的影响:
假设P1、P2是同一个类的不同对象,这个类中定义了以下几种情况的同步块或同步方法,P1、P2就都可以调用它们。
1. 把synchronized当作函数修饰符时,示例代码如下:
Public synchronized void methodAAA(){//….}这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。
上边的示例代码等同于如下代码:
public void methodAAA(){synchronized (this) // (1){ //…..}}(1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(
2.同步块,示例代码如下:
public void method3(SomeObject so) { synchronized(so){ //…..}}这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:
class Foo implements Runnable{ private byte[] lock = new byte[0]; // 特殊的instance变量Public void methodA(){ synchronized(lock) { //… }}//…..}注:零长度的byte数组对象创建起来将比任何对象都经济查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
3.将synchronized作用于static 函数,示例代码如下:
Class Foo{public synchronized static void methodAAA() // 同步的static 函数{//….}public void methodBBB(){ synchronized(Foo.class) // class literal(类名称字面常量)} }代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。
记得在《Effective Java》一书中看到过将Foo.class和P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。
可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。
1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。
2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他非同步方法。
3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
4、对于同步,要时刻清醒在哪个对象上同步,这是关键。
5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。
九、线程数据传递
在传统的同步开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果。但在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别。由于线程的运行和结束是不可预料的,因此,在传递和返回数据时就无法象函数一样通过函数参数和return语句来返回数据。
9.1、通过构造方法传递数据在创建线程时,必须要建立一个Thread类的或其子类的实例。因此,我们不难想到在调用start方法之前通过线程类的构造方法将数据传入线程。并将传入的数据使用类变量保存起来,以便线程使用(其实就是在run方法中使用)。下面的代码演示了如何通过构造方法来传递数据:
package mythread; public class MyThread1 extends Thread { private String name; public MyThread1(String name) { this.name = name; } public void run() { System.out.println("hello " + name); } public static void main(String[] args) { Thread thread = new MyThread1("world"); thread.start(); }}由于这种方法是在创建线程对象的同时传递数据的,因此,在线程运行之前这些数据就就已经到位了,这样就不会造成数据在线程运行后才传入的现象。如果要传递更复杂的数据,可以使用集合、类等数据结构。使用构造方法来传递数据虽然比较安全,但如果要传递的数据比较多时,就会造成很多不便。由于Java没有默认参数,要想实现类似默认参数的效果,就得使用重载,这样不但使构造方法本身过于复杂,又会使构造方法在数量上大增。因此,要想避免这种情况,就得通过类方法或类变量来传递数据。
9.2、通过变量和方法传递数据
向对象中传入数据一般有两次机会,第一次机会是在建立对象时通过构造方法将数据传入,另外一次机会就是在类中定义一系列的public的方法或变量(也可称之为字段)。然后在建立完对象后,通过对象实例逐个赋值。下面的代码是对MyThread1类的改版,使用了一个setName方法来设置name变量:
package mythread; public class MyThread2 implements Runnable { private String name; public void setName(String name) { this.name = name; } public void run() { System.out.println("hello " + name); } public static void main(String[] args) { MyThread2 myThread = new MyThread2(); myThread.setName("world"); Thread thread = new Thread(myThread); thread.start(); }} 9.3、通过回调函数传递数据
上面讨论的两种向线程中传递数据的方法是最常用的。但这两种方法都是main方法中主动将数据传入线程类的。这对于线程来说,是被动接收这些数据的。然而,在有些应用中需要在线程运行的过程中动态地获取数据,如在下面代码的run方法中产生了3个随机数,然后通过Work类的process方法求这三个随机数的和,并通过Data类的value将结果返回。从这个例子可以看出,在返回value之前,必须要得到三个随机数。也就是说,这个value是无法事先就传入线程类的。
package mythread; class Data { public int value = 0; } class Work { public void process(Data data, Integer numbers) { for (int n : numbers) { data.value += n; } } } public class MyThread3 extends Thread { private Work work; public MyThread3(Work work) { this.work = work; } public void run() { java.util.Random random = new java.util.Random(); Data data = new Data(); int n1 = random.nextInt(1000); int n2 = random.nextInt(2000); int n3 = random.nextInt(3000); work.process(data, n1, n2, n3); // Use the callback function System.out.println(String.valueOf(n1) + "+" + String.valueOf(n2) + "+" + String.valueOf(n3) + "=" + data.value); } public static void main(String[] args) { Thread thread = new MyThread3(new Work()); thread.start(); }}The above is a detailed explanation of Java multi-threading. I hope it can help you learn this part of the knowledge. Terima kasih atas dukungan Anda untuk situs web ini!