Java 101: Memahami thread Java, Bagian 1: Memperkenalkan thread dan runnable

Artikel ini adalah yang pertama dari seri empat bagian Java 101 yang menjelajahi utas Java. Meskipun Anda mungkin berpikir threading di Java akan sulit untuk dipahami, saya bermaksud menunjukkan kepada Anda bahwa utas mudah dipahami. Pada artikel ini, saya memperkenalkan Anda ke utas Java dan runnable. Dalam artikel selanjutnya, kita akan membahas sinkronisasi (melalui kunci), masalah sinkronisasi (seperti kebuntuan), mekanisme tunggu / pemberitahuan, penjadwalan (dengan dan tanpa prioritas), gangguan utas, timer, volatilitas, grup utas, dan variabel lokal utas .

Perhatikan bahwa artikel ini (bagian dari arsip JavaWorld) diperbarui dengan daftar kode baru dan kode sumber yang dapat diunduh pada Mei 2013.

Memahami utas Java - baca keseluruhan seri

  • Bagian 1: Memperkenalkan utas dan runnable
  • Bagian 2: Sinkronisasi
  • Bagian 3: Penjadwalan utas dan tunggu / beri tahu
  • Bagian 4: Grup thread dan volatilitas

Apa itu utas?

Secara konseptual, gagasan tentang utas tidak sulit untuk dipahami: ini adalah jalur eksekusi independen melalui kode program. Ketika beberapa utas dijalankan, jalur satu utas melalui kode yang sama biasanya berbeda dari yang lain. Misalnya, satu utas mengeksekusi kode byte yang setara dari bagian pernyataan if-else if, sementara utas lain mengeksekusi kode byte yang setara dari elsebagian tersebut. Bagaimana JVM melacak eksekusi setiap utas? JVM memberi setiap thread tumpukan metode-panggilannya sendiri. Selain melacak instruksi kode byte saat ini, tumpukan panggilan metode melacak variabel lokal, parameter yang diteruskan JVM ke metode, dan nilai kembalian metode.

Ketika beberapa utas mengeksekusi urutan instruksi kode byte dalam program yang sama, tindakan itu dikenal sebagai multithreading . Multithreading menguntungkan program dalam berbagai cara:

  • Program berbasis multithreaded GUI (antarmuka pengguna grafis) tetap responsif terhadap pengguna saat melakukan tugas lain, seperti memberi tag ulang atau mencetak dokumen.
  • Program berulir biasanya selesai lebih cepat daripada program yang belum dibaca. Hal ini terutama berlaku untuk utas yang berjalan pada mesin multiprosesor, di mana setiap utas memiliki prosesornya sendiri.

Java menyelesaikan multithreading melalui java.lang.Threadkelasnya. Setiap Threadobjek menggambarkan satu rangkaian eksekusi. Eksekusi yang terjadi di Thread's run()metode. Karena run()metode default tidak melakukan apa pun, Anda harus membuat subkelas Threaddan mengganti run()untuk menyelesaikan pekerjaan yang berguna. Untuk rasa utas dan multithreading dalam konteks Thread, periksa Daftar 1:

Kode 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'); } } }

Kode 1 menyajikan kode sumber untuk aplikasi yang terdiri dari kelas ThreadDemodan MyThread. Kelas ThreadDemomenjalankan aplikasi dengan membuat MyThreadobjek, memulai utas yang terkait dengan objek itu, dan menjalankan beberapa kode untuk mencetak tabel kotak. Sebaliknya, MyThreadmenimpa Thread's run()metode untuk mencetak (pada output stream standar) segitiga sudut kanan terdiri dari karakter tanda bintang.

Penjadwalan thread dan JVM

Sebagian besar (jika tidak semua) implementasi JVM menggunakan kemampuan threading platform yang mendasarinya. Karena kapabilitas tersebut khusus platform, urutan keluaran program multithread Anda mungkin berbeda dari urutan keluaran orang lain. Perbedaan itu dihasilkan dari penjadwalan, topik yang akan saya bahas nanti di seri ini.

Saat Anda mengetik java ThreadDemountuk menjalankan aplikasi, JVM membuat thread awal eksekusi, yang menjalankan main()metode tersebut. Dengan mengeksekusi mt.start ();, thread awal memberi tahu JVM untuk membuat thread kedua eksekusi yang mengeksekusi instruksi kode byte yang terdiri MyThreaddari run()metode objek. Saat start()metode kembali, thread awal menjalankan forloop untuk mencetak tabel persegi, sedangkan thread baru menjalankan run()metode untuk mencetak segitiga siku-siku.

Seperti apa keluarannya? Jalankan ThreadDemountuk mencari tahu. Anda akan melihat keluaran setiap utas cenderung diselingi dengan keluaran lainnya. Itu terjadi karena kedua utas mengirim outputnya ke aliran output standar yang sama.

Kelas Thread

Untuk menjadi ahli dalam menulis kode multithread, Anda harus terlebih dahulu memahami berbagai metode yang membentuk Threadkelas. Bagian ini membahas banyak dari metode tersebut. Secara khusus, Anda mempelajari tentang metode untuk memulai utas, memberi nama utas, meletakkan utas ke mode tidur, menentukan apakah utas hidup, menggabungkan satu utas ke utas lain, dan menghitung semua utas aktif dalam grup utas dan subkelompok utas saat ini. Saya juga membahas Threadalat bantu debugging dan utas pengguna versus utas daemon.

Saya akan menyajikan sisa Threadmetode di artikel berikutnya, dengan pengecualian metode usang Sun.

Metode yang tidak digunakan lagi

Sun telah menghentikan berbagai Threadmetode, seperti suspend()dan resume(), karena metode tersebut dapat mengunci program Anda atau merusak objek. Akibatnya, Anda tidak boleh memanggilnya dalam kode Anda. Lihat dokumentasi SDK untuk solusi untuk metode tersebut. Saya tidak membahas metode yang tidak digunakan lagi dalam seri ini.

Membuat benang

Threadmemiliki delapan konstruktor. Yang paling sederhana adalah:

  • Thread(), yang membuat Threadobjek dengan nama default
  • Thread(String name), yang membuat Threadobjek dengan nama yang nameditentukan oleh argumen

Konstruktor paling sederhana berikutnya adalah Thread(Runnable target)dan Thread(Runnable target, String name). Terlepas dari Runnableparameternya, konstruktor tersebut identik dengan konstruktor tersebut. Perbedaannya: RunnableParameter mengidentifikasi objek di luar Threadyang menyediakan run()metode. (Anda belajar tentang Runnablenanti dalam artikel ini.) The akhir empat konstruktor menyerupai Thread(String name), Thread(Runnable target)dan Thread(Runnable target, String name); Namun, konstruktor terakhir juga menyertakan ThreadGroupargumen untuk tujuan organisasi.

Salah satu dari empat konstruktor terakhir Thread(ThreadGroup group, Runnable target, String name, long stackSize),, menarik karena memungkinkan Anda menentukan ukuran stack metode-panggilan thread yang diinginkan. Mampu menentukan ukuran tersebut terbukti membantu dalam program dengan metode yang memanfaatkan rekursi — teknik eksekusi di mana metode berulang kali memanggil dirinya sendiri — untuk memecahkan masalah tertentu secara elegan. Dengan menyetel ukuran tumpukan secara eksplisit, terkadang Anda dapat mencegah StackOverflowErrors. Namun, ukuran yang terlalu besar dapat menyebabkan OutOfMemoryErrors. Selain itu, Sun menganggap ukuran tumpukan panggilan metode sebagai bergantung pada platform. Bergantung pada platformnya, ukuran tumpukan panggilan metode mungkin berubah. Oleh karena itu, pikirkan baik-baik tentang konsekuensi program Anda sebelum menulis kode yang memanggil Thread(ThreadGroup group, Runnable target, String name, long stackSize).

Nyalakan kendaraan Anda

Benang menyerupai kendaraan: mereka memindahkan program dari awal hingga akhir. Threaddan Threadobjek subclass bukanlah utas. Sebaliknya, mereka mendeskripsikan atribut thread, seperti namanya, dan berisi kode (melalui run()metode) yang dijalankan thread. Ketika saatnya tiba untuk thread baru untuk dieksekusi run(), thread lain memanggil metode Threadobjek subkelas atau s start(). Misalnya, untuk memulai thread kedua, thread awal aplikasi — yang menjalankan — main()memanggil start(). Sebagai tanggapan, kode penanganan utas JVM bekerja dengan platform untuk memastikan utas menginisialisasi dengan benar dan memanggil metode Threadobjek subkelas atau a 's run().

Setelah start()selesai, beberapa utas dijalankan. Karena kita cenderung berpikir secara linier, kita sering merasa kesulitan untuk memahami aktivitas serentak (simultan) yang terjadi ketika dua atau lebih thread sedang berjalan. Oleh karena itu, Anda harus memeriksa bagan yang menunjukkan di mana utas dijalankan (posisinya) versus waktu. Gambar di bawah ini menampilkan bagan seperti itu.

Bagan menunjukkan beberapa periode waktu yang signifikan:

  • Inisialisasi utas awal
  • Saat utas itu mulai dijalankan main()
  • Saat utas itu mulai dijalankan start()
  • Momen start()membuat utas baru dan kembali kemain()
  • Inisialisasi utas baru
  • Saat utas baru mulai dijalankan run()
  • Momen berbeda setiap utas berakhir

Perhatikan bahwa inisialisasi thread baru, pelaksanaannya run(), dan penghentiannya terjadi bersamaan dengan eksekusi thread awal. Perhatikan juga bahwa setelah thread memanggil start(), panggilan berikutnya ke metode tersebut sebelum run()metode keluar menyebabkan start()lemparan java.lang.IllegalThreadStateExceptionobjek.

Apa arti sebuah nama?

Selama sesi debugging, membedakan satu utas dari utas lainnya dengan cara yang ramah pengguna terbukti membantu. Untuk membedakan di antara utas, Java mengaitkan nama dengan utas. Nama itu secara default adalah Thread, karakter tanda hubung, dan bilangan bulat berbasis nol. Anda dapat menerima nama utas default Java atau Anda dapat memilih sendiri. Untuk mengakomodasi nama kustom, Threadsediakan konstruktor yang mengambil nameargumen dan setName(String name)metode. Threadjuga menyediakan getName()metode yang mengembalikan nama saat ini. Kode 2 menunjukkan bagaimana membuat nama kustom melalui Thread(String name)konstruktor dan mengambil nama saat ini dalam run()metode dengan memanggil getName():

Kode 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 ()); } }

Anda dapat meneruskan argumen nama opsional ke MyThreadpada baris perintah. Misalnya, java NameThatThread Xditetapkan Xsebagai nama utas. Jika Anda gagal menentukan nama, Anda akan melihat output berikut:

My name is: Thread-1

Jika mau, Anda dapat mengubah super (name);panggilan dalam MyThread (String name)konstruktor menjadi panggilan ke setName (String name)—seperti di setName (name);. Pemanggilan metode terakhir mencapai tujuan yang sama — menetapkan nama utas — sebagai super (name);. Saya tinggalkan itu sebagai latihan untuk Anda.

Penamaan utama

Java memberikan nama mainke thread yang menjalankan main()metode ini, thread awal. Anda biasanya melihat nama itu dalam Exception in thread "main"pesan yang dicetak oleh pengendali pengecualian default JVM saat utas awal melempar objek pengecualian.

Untuk tidur atau tidak

Nanti di kolom ini, saya akan memperkenalkan Anda pada animasi - menggambar berulang kali pada satu gambar permukaan yang sedikit berbeda satu sama lain untuk mendapatkan ilusi gerakan. Untuk menyelesaikan animasi, utas harus berhenti selama menampilkan dua gambar berturut-turut. Metode Threadstatis panggilan sleep(long millis)memaksa utas berhenti selama millismilidetik. Benang lain mungkin bisa mengganggu benang tidur. Jika itu terjadi, benang tidur akan membangunkan dan melempar InterruptedExceptionobjek dari sleep(long millis)metode. Akibatnya, kode yang memanggil sleep(long millis)harus muncul di dalam tryblok — atau metode kode harus disertakan InterruptedExceptiondalam throwsklausulnya.

Untuk mendemonstrasikan sleep(long millis), saya telah menulis CalcPI1aplikasi. Aplikasi itu memulai utas baru yang menggunakan algoritme matematika untuk menghitung nilai pi konstanta matematika. Saat utas baru menghitung, utas awal akan dijeda selama 10 milidetik dengan memanggil sleep(long millis). Setelah thread awal terbangun, ia mencetak nilai pi, yang disimpan oleh thread baru dalam variabel pi. Kode sumber 3 menyajikan CalcPI1:

Daftar 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"); } }

Jika Anda menjalankan program ini, Anda akan melihat keluaran yang mirip (tetapi mungkin tidak identik) dengan berikut ini:

pi = -0.2146197014017295 Finished calculating PI