Java 101: Memahami benang Java, Bahagian 1: Memperkenalkan benang dan runnables
Artikel ini adalah yang pertama dalam siri Java 101 empat bahagian yang meneroka benang Java. Walaupun anda mungkin menganggap threading di Jawa sangat sukar untuk dipahami, saya ingin menunjukkan kepada anda bahawa benang mudah difahami. Dalam artikel ini, saya memperkenalkan anda pada thread Java dan runnables. Dalam artikel seterusnya, kami akan meneroka penyegerakan (melalui kunci), masalah penyegerakan (seperti kebuntuan), mekanisme tunggu / maklumkan, penjadualan (dengan dan tanpa keutamaan), gangguan benang, pemasa, turun naik, kumpulan utas, dan benang pemboleh ubah tempatan .
Perhatikan bahawa artikel ini (sebahagian daripada arkib JavaWorld) telah dikemas kini dengan senarai kod baru dan kod sumber yang boleh dimuat turun pada bulan Mei 2013.
Memahami benang Java - baca keseluruhan siri
- Bahagian 1: Memperkenalkan benang dan runnables
- Bahagian 2: Penyegerakan
- Bahagian 3: Penjadualan benang dan tunggu / maklumkan
- Bahagian 4: Kumpulan benang dan turun naik
Apa itu utas?
Secara konseptual, konsep benang tidak sukar untuk dipahami: ini adalah jalan pelaksanaan yang bebas melalui kod program. Apabila banyak utas dijalankan, jalan satu utas melalui kod yang sama biasanya berbeza dari yang lain. Sebagai contoh, anggap satu utas melaksanakan kod bait yang setara dengan bahagian pernyataan if-else if
, sementara utas lain melaksanakan kod bait yang setara dengan else
bahagian tersebut. Bagaimana JVM mengawasi pelaksanaan setiap utas? JVM memberikan setiap thread kaedah panggilan-panggilannya sendiri. Sebagai tambahan untuk melacak arahan kod byte semasa, tumpukan kaedah-kaedah mengesan pemboleh ubah tempatan, parameter yang diteruskan JVM ke suatu metode, dan nilai pengembalian metode.
Apabila beberapa utas menjalankan urutan arahan kod byte dalam program yang sama, tindakan itu dikenali sebagai multithreading . Multithreading memberi manfaat kepada program dengan pelbagai cara:
- Program berasaskan GUI multithreaded (antara muka pengguna grafik) tetap responsif kepada pengguna ketika melakukan tugas lain, seperti melakukan repaginasi atau mencetak dokumen.
- Program berulir biasanya selesai lebih cepat daripada rakan mereka yang tidak diulirkan. Ini terutama berlaku untuk benang yang berjalan pada mesin multiprosesor, di mana setiap utas mempunyai pemprosesnya sendiri.
Java mencapai multithreading melalui java.lang.Thread
kelasnya. Setiap Thread
objek menerangkan satu utas pelaksanaan. Pelaksanaan yang berlaku dalam Thread
's run()
kaedah. Kerana run()
kaedah lalai tidak melakukan apa-apa, anda mesti menundukkan kelas Thread
dan mengatasi run()
untuk menyelesaikan kerja yang bermanfaat. Untuk rasa benang dan multithreading dalam konteks Thread
, periksa Penyenaraian 1:
Penyenaraian 1. ThreadDemo.java
// ThreadDemo.java class ThreadDemo { public static void main (String [] args) { MyThread mt = new MyThread (); mt.start (); for (int i = 0; i < 50; i++) System.out.println ("i = " + i + ", i * i = " + i * i); } } class MyThread extends Thread { public void run () { for (int count = 1, row = 1; row < 20; row++, count++) { for (int i = 0; i < count; i++) System.out.print ('*'); System.out.print ('\n'); } } }
Penyenaraian 1 menunjukkan kod sumber untuk aplikasi yang terdiri daripada kelas ThreadDemo
dan MyThread
. Kelas ThreadDemo
mendorong aplikasi dengan membuat MyThread
objek, memulakan utas yang berkait dengan objek itu, dan melaksanakan beberapa kod untuk mencetak jadual kotak. Sebaliknya, MyThread
mengatasi Thread
's run()
kaedah untuk mencetak (atas aliran output standard) sebuah segitiga hak-sudut terdiri daripada aksara asterisk.
Penjadualan benang dan JVM
Sebilangan besar (jika tidak semua) pelaksanaan JVM menggunakan keupayaan threading platform yang mendasari. Kerana kemampuan tersebut khusus platform, urutan output program multithread anda mungkin berbeza dari urutan output orang lain. Perbezaan itu berpunca daripada penjadualan, topik yang saya terokai kemudian dalam siri ini.
Apabila anda menaip java ThreadDemo
untuk menjalankan aplikasi, JVM membuat permulaan pelaksanaan, yang melaksanakan main()
kaedah tersebut. Dengan melaksanakan mt.start ();
, utas permulaan memberitahu JVM untuk membuat urutan pelaksanaan kedua yang melaksanakan arahan kod bait yang merangkumi kaedah MyThread
objek run()
. Apabila start()
kaedah kembali, utas permulaan menjalankan for
gelungnya untuk mencetak meja kotak, sementara utas baru melaksanakan run()
kaedah untuk mencetak segitiga sudut kanan.
Seperti apa outputnya? Lari ThreadDemo
untuk mengetahui. Anda akan melihat output setiap benang cenderung bersilang dengan output yang lain. Itu hasil kerana kedua-dua utas menghantar outputnya ke aliran output standard yang sama.
Kelas Thread
Untuk menjadi mahir dalam menulis kod multithread, anda mesti terlebih dahulu memahami pelbagai kaedah yang membentuk Thread
kelas. Bahagian ini menerangkan banyak kaedah tersebut. Secara khusus, anda belajar mengenai kaedah untuk memulakan utas, menamakan benang, meletakkan benang untuk tidur, menentukan sama ada benang masih hidup, menggabungkan satu utas ke utas yang lain, dan menghitung semua utas aktif dalam kumpulan dan subkumpulan utas semasa. Saya juga membincangkan Thread
alat bantu debug dan utas pengguna berbanding benang daemon.
Saya akan membentangkan sisa Thread
kaedah dalam artikel berikutnya, kecuali kaedah Sun yang tidak digunakan lagi.
Kaedah yang tidak digunakan lagi
Sun telah menghentikan penggunaan berbagai Thread
metode, seperti suspend()
dan resume()
, kerana mereka dapat mengunci program anda atau merosakkan objek. Akibatnya, anda tidak boleh memanggilnya dalam kod anda. Rujuk dokumentasi SDK untuk penyelesaian kaedah tersebut. Saya tidak merangkumi kaedah yang tidak digunakan dalam siri ini.
Membina benang
Thread
mempunyai lapan pembina. Yang paling mudah adalah:
Thread()
, yang membuatThread
objek dengan nama lalaiThread(String name)
, yang membuatThread
objek dengan nama yangname
ditentukan oleh argumen
Pembina termudah seterusnya ialah Thread(Runnable target)
dan Thread(Runnable target, String name)
. Terlepas dari Runnable
parameternya, konstruktor tersebut sama dengan pembina yang disebutkan di atas. Perbezaannya: Runnable
Parameter mengenal pasti objek di luar Thread
yang menyediakan run()
kaedah. (Anda belajar tentang Runnable
kemudian dalam artikel ini.) Final empat pengeluar menyerupai Thread(String name)
, Thread(Runnable target)
dan Thread(Runnable target, String name)
; namun, pembina akhir juga menyertakan ThreadGroup
hujah untuk tujuan organisasi.
Salah satu daripada empat pembina terakhir Thread(ThreadGroup group, Runnable target, String name, long stackSize)
, menarik kerana ia membolehkan anda menentukan ukuran yang dikehendaki dari timbunan kaedah panggilan. Mampu menentukan ukuran itu terbukti bermanfaat dalam program dengan kaedah yang menggunakan rekursi - teknik pelaksanaan di mana kaedah berulang kali memanggil dirinya sendiri - untuk menyelesaikan masalah tertentu dengan elegan. Dengan menetapkan ukuran timbunan secara eksplisit, kadangkala anda dapat mencegah StackOverflowError
s. Namun, ukuran yang terlalu besar boleh mengakibatkan OutOfMemoryError
s. Juga, Sun menganggap ukuran timbunan panggilan kaedah bergantung pada platform. Bergantung pada platform, ukuran tumpukan kaedah panggilan mungkin berubah. Oleh itu, fikirkan dengan teliti mengenai kesan ke atas program anda sebelum menulis kod yang memanggil Thread(ThreadGroup group, Runnable target, String name, long stackSize)
.
Mulakan kenderaan anda
Benang menyerupai kenderaan: mereka memindahkan program dari awal hingga akhir. Thread
dan Thread
objek subkelas bukan utas. Sebaliknya, mereka menerangkan atribut utas, seperti namanya, dan mengandungi kod (melalui run()
kaedah) yang dijalankan oleh utas. Apabila tiba masanya utas baru dijalankan run()
, utas lain memanggil kaedah Thread
objek subclass start()
. Sebagai contoh, untuk memulakan main()
utas kedua, utas permulaan aplikasi — yang melaksanakan — panggilan start()
. Sebagai tindak balas, kod pengendalian benang JVM berfungsi dengan platform untuk memastikan utas memulakan dan memanggil kaedah Thread
objek subclass dengan betul run()
.
Setelah start()
selesai, pelbagai utas dilaksanakan. Oleh kerana kita cenderung berfikir secara linear, kita sering merasa sukar untuk memahami aktiviti serentak (serentak) yang berlaku semasa dua atau lebih utas berjalan. Oleh itu, anda harus memeriksa carta yang menunjukkan di mana utas menjalankan (kedudukannya) berbanding masa. Rajah di bawah menunjukkan carta sedemikian.

Carta menunjukkan beberapa jangka masa yang signifikan:
- Permulaan benang permulaan
- Momen benang mula dilaksanakan
main()
- Momen benang mula dilaksanakan
start()
- Momen itu
start()
mencipta utas baru dan kembali kemain()
- Permulaan thread baru
- Sebaik sahaja thread baru mula dilaksanakan
run()
- Momen berbeza setiap thread berakhir
Perhatikan bahawa inisialisasi thread baru, pelaksanaannya run()
, dan penamatannya berlaku serentak dengan pelaksanaan thread permulaan. Juga perhatikan bahawa setelah panggilan utas start()
, panggilan berikutnya ke kaedah itu sebelum run()
kaedah keluar menyebabkan start()
melempar java.lang.IllegalThreadStateException
objek.
Apa namanya?
During a debugging session, distinguishing one thread from another in a user-friendly fashion proves helpful. To differentiate among threads, Java associates a name with a thread. That name defaults to Thread
, a hyphen character, and a zero-based integer number. You can accept Java's default thread names or you can choose your own. To accommodate custom names, Thread
provides constructors that take name
arguments and a setName(String name)
method. Thread
also provides a getName()
method that returns the current name. Listing 2 demonstrates how to establish a custom name via the Thread(String name)
constructor and retrieve the current name in the run()
method by calling getName()
:
Listing 2. NameThatThread.java
// NameThatThread.java class NameThatThread { public static void main (String [] args) { MyThread mt; if (args.length == 0) mt = new MyThread (); else mt = new MyThread (args [0]); mt.start (); } } class MyThread extends Thread { MyThread () { // The compiler creates the byte code equivalent of super (); } MyThread (String name) { super (name); // Pass name to Thread superclass } public void run () { System.out.println ("My name is: " + getName ()); } }
You can pass an optional name argument to MyThread
on the command line. For example, java NameThatThread X
establishes X
as the thread's name. If you fail to specify a name, you'll see the following output:
My name is: Thread-1
If you prefer, you can change the super (name);
call in the MyThread (String name)
constructor to a call to setName (String name)
—as in setName (name);
. That latter method call achieves the same objective—establishing the thread's name—as super (name);
. I leave that as an exercise for you.
Naming main
Java assigns the name main
to the thread that runs the main()
method, the starting thread. You typically see that name in the Exception in thread "main"
message that the JVM's default exception handler prints when the starting thread throws an exception object.
To sleep or not to sleep
Later in this column, I will introduce you to animation— repeatedly drawing on one surface images that slightly differ from each other to achieve a movement illusion. To accomplish animation, a thread must pause during its display of two consecutive images. Calling Thread
's static sleep(long millis)
method forces a thread to pause for millis
milliseconds. Another thread could possibly interrupt the sleeping thread. If that happens, the sleeping thread awakes and throws an InterruptedException
object from the sleep(long millis)
method. As a result, code that calls sleep(long millis)
must appear within a try
block—or the code's method must include InterruptedException
in its throws
clause.
Untuk menunjukkan sleep(long millis)
, saya telah menulis CalcPI1
permohonan. Aplikasi itu memulakan utas baru yang menggunakan algoritma matematik untuk mengira nilai pi pemalar matematik. Semasa thread baru mengira, utas permulaan berhenti selama 10 milisaat dengan memanggil sleep(long millis)
. Setelah benang permulaan terjaga, ia mencetak nilai pi, yang benang baru simpan dalam pemboleh ubah pi
. Penyenaraian 3 menyajikan CalcPI1
kod sumber:
Penyenaraian 3. CalcPI1.java
// CalcPI1.java class CalcPI1 { public static void main (String [] args) { MyThread mt = new MyThread (); mt.start (); try { Thread.sleep (10); // Sleep for 10 milliseconds } catch (InterruptedException e) { } System.out.println ("pi = " + mt.pi); } } class MyThread extends Thread { boolean negative = true; double pi; // Initializes to 0.0, by default public void run () { for (int i = 3; i < 100000; i += 2) { if (negative) pi -= (1.0 / i); else pi += (1.0 / i); negative = !negative; } pi += 1.0; pi *= 4.0; System.out.println ("Finished calculating PI"); } }
Sekiranya anda menjalankan program ini, anda akan melihat output yang serupa (tetapi mungkin tidak serupa) dengan yang berikut:
pi = -0.2146197014017295 Finished calculating PI