Perkenalan
Multithreading dibangun ke dalam Java sejak awal. Jadi, mari kita lihat secara singkat hal yang disebut multithreading ini.
![Lebih baik bersama: Java dan kelas Thread. Bagian I — Utas eksekusi - 1]()
Kami mengambil pelajaran resmi dari Oracle sebagai referensi: "
Pelajaran: Aplikasi "Halo Dunia!" ". Kami akan sedikit mengubah kode program Hello World kami sebagai berikut:
class HelloWorldApp {
public static void main(String[] args) {
System.out.println("Hello, " + args[0]);
}
}
args
adalah larik parameter masukan yang diteruskan saat program dimulai. Simpan kode ini ke file dengan nama yang sesuai dengan nama kelas dan memiliki ekstensi
.java
. Kompilasi menggunakan utilitas
javac :
javac HelloWorldApp.java
. Kemudian, kami menjalankan kode kami dengan beberapa parameter, misalnya, "Roger":
java HelloWorldApp Roger
![Lebih baik bersama: Java dan kelas Thread. Bagian I — Utas eksekusi - 2]()
Kode kami saat ini memiliki cacat serius. Jika Anda tidak memberikan argumen apa pun (mis. jalankan saja "java HelloWorldApp"), maka kami mendapatkan kesalahan:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
at HelloWorldApp.main(HelloWorldApp.java:3)
Pengecualian (yaitu kesalahan) terjadi di utas bernama "main". Jadi, Java punya utas? Disinilah perjalanan kita dimulai.
Jawa dan benang
Untuk memahami apa itu utas, Anda perlu memahami bagaimana program Java dimulai. Mari ubah kode kita sebagai berikut:
class HelloWorldApp {
public static void main(String[] args) {
while (true) {
}
}
}
Sekarang mari kita kompilasi lagi dengan
javac
. Untuk kenyamanan, kami akan menjalankan kode Java kami di jendela terpisah. Di Windows, ini bisa dilakukan seperti ini:
start java HelloWorldApp
. Sekarang kita akan menggunakan utilitas
jps untuk melihat informasi apa yang dapat diberikan Java kepada kita:
![Lebih baik bersama: Java dan kelas Thread. Bagian I — Utas eksekusi - 3]()
Angka pertama adalah PID atau ID Proses. Apa itu proses?
A process is a combination of code and data sharing a common virtual address space.
Dengan proses, program yang berbeda diisolasi satu sama lain saat dijalankan: setiap aplikasi menggunakan areanya sendiri di memori tanpa mengganggu program lain. Untuk mempelajari lebih lanjut, saya sarankan membaca tutorial ini:
Processes and Threads . Suatu proses tidak dapat ada tanpa thread, jadi jika suatu proses ada, maka proses tersebut memiliki setidaknya satu thread. Tapi bagaimana ini terjadi di Jawa? Saat kita memulai program Java, eksekusi dimulai dengan
main
metode. Seolah-olah kita sedang melangkah ke dalam program, jadi
main
metode khusus ini disebut titik masuk. Metodenya
main
harus selalu "public static void", sehingga mesin virtual Java (JVM) dapat mulai menjalankan program kita. Untuk informasi lebih lanjut,
Mengapa metode utama Java statis?. Ternyata peluncur Java (java.exe atau javaw.exe) adalah aplikasi C sederhana: ini memuat berbagai DLL yang sebenarnya terdiri dari JVM. Peluncur Java membuat serangkaian panggilan Java Native Interface (JNI) tertentu. JNI adalah mekanisme untuk menghubungkan dunia mesin virtual Java dengan dunia C++. Jadi, launcher bukanlah JVM itu sendiri, melainkan sebuah mekanisme untuk memuatnya. Ia tahu perintah yang benar untuk dijalankan untuk memulai JVM. Ia tahu cara menggunakan panggilan JNI untuk menyiapkan lingkungan yang diperlukan. Menyiapkan lingkungan ini termasuk membuat utas utama, yang tentu saja disebut "utama". Untuk mengilustrasikan utas mana yang ada dalam proses Java dengan lebih baik, kami menggunakan
jvisualvmalat, yang disertakan dengan JDK. Mengetahui pid suatu proses, kita dapat segera melihat informasi tentang proses itu:
jvisualvm --openpid <process id>
![Lebih baik bersama: Java dan kelas Thread. Bagian I — Utas eksekusi - 4]()
Menariknya, setiap utas memiliki area tersendiri dalam memori yang dialokasikan untuk proses tersebut. Struktur memori ini disebut stack. Tumpukan terdiri dari bingkai. Bingkai mewakili aktivasi metode (panggilan metode yang belum selesai). Frame juga dapat direpresentasikan sebagai StackTraceElement (lihat API Java untuk
StackTraceElement ). Anda dapat menemukan informasi lebih lanjut tentang memori yang dialokasikan untuk setiap utas dalam diskusi di sini: "
Bagaimana Java (JVM) mengalokasikan tumpukan untuk setiap utas ". Jika Anda melihat
Java API dan mencari kata "Thread", Anda akan menemukan
java.lang.Threadkelas. Ini adalah kelas yang mewakili utas di Jawa, dan kita harus bekerja dengannya.
java.lang.Thread
Di Jawa, sebuah utas diwakili oleh turunan dari kelas
java.lang.Thread
. Anda harus segera memahami bahwa instance dari kelas Thread itu sendiri bukanlah thread eksekusi. Ini hanyalah semacam API untuk utas tingkat rendah yang dikelola oleh JVM dan sistem operasi. Saat kita memulai JVM menggunakan peluncur Java, JVM membuat
main
utas bernama "utama" dan beberapa utas housekeeping lainnya. Seperti yang dinyatakan dalam JavaDoc untuk kelas Thread:
When a Java Virtual Machine starts up, there is usually a single non-daemon thread
. Ada 2 jenis utas: daemon dan non-daemon. Utas daemon adalah utas latar belakang (tata graha) yang melakukan beberapa pekerjaan di latar belakang. Kata "daemon" mengacu pada iblis Maxwell. Anda dapat mempelajari lebih lanjut di
artikel Wikipedia ini . Sebagaimana dinyatakan dalam dokumentasi, JVM terus menjalankan program (proses) hingga:
- Metode Runtime.exit () dipanggil
- Semua utas NON-daemon menyelesaikan pekerjaannya (tanpa kesalahan atau dengan pengecualian yang dilemparkan)
Detail penting mengikuti dari ini: utas daemon dapat diakhiri kapan saja. Akibatnya, tidak ada jaminan tentang integritas data mereka. Karenanya, utas daemon cocok untuk tugas rumah tangga tertentu. Misalnya, Java memiliki utas yang bertanggung jawab untuk memproses pemanggilan
finalize()
metode, yaitu utas yang terkait dengan Garbage Collector (gc). Setiap utas adalah bagian dari grup (
ThreadGroup ). Dan kelompok dapat menjadi bagian dari kelompok lain, membentuk hierarki atau struktur tertentu.
public static void main(String[] args) {
Thread currentThread = Thread.currentThread();
ThreadGroup threadGroup = currentThread.getThreadGroup();
System.out.println("Thread: " + currentThread.getName());
System.out.println("Thread Group: " + threadGroup.getName());
System.out.println("Parent Group: " + threadGroup.getParent().getName());
}
Grup menertibkan manajemen utas. Selain grup, utas memiliki penangan pengecualiannya sendiri. Lihatlah contoh:
public static void main(String[] args) {
Thread th = Thread.currentThread();
th.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("An error occurred: " + e.getMessage());
}
});
System.out.println(2/0);
}
Pembagian dengan nol akan menimbulkan kesalahan yang akan ditangkap oleh pawang. Jika Anda tidak menentukan penangan Anda sendiri, maka JVM akan memanggil penangan default, yang akan menampilkan jejak tumpukan pengecualian ke StdError. Setiap utas juga memiliki prioritas. Anda dapat membaca lebih lanjut tentang prioritas di artikel ini:
Java Thread Priority in Multithreading .
Membuat utas
Sebagaimana dinyatakan dalam dokumentasi, kami memiliki 2 cara untuk membuat utas. Cara pertama adalah membuat subclass Anda sendiri. Misalnya:
public class HelloWorld{
public static class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello, World!");
}
}
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
}
}
Seperti yang Anda lihat, pekerjaan tugas terjadi di dalam
run()
metode, tetapi utas itu sendiri dimulai di dalam
start()
metode. Jangan bingung metode ini: jika kita memanggil
un()
metode r secara langsung, maka tidak ada utas baru yang akan dimulai. Ini adalah
start()
metode yang meminta JVM untuk membuat utas baru. Opsi di mana kita mewarisi Thread ini sudah buruk karena kita menyertakan Thread dalam hierarki kelas kita. Kelemahan kedua adalah kita mulai melanggar prinsip "tanggung jawab tunggal". Yaitu, kelas kami secara bersamaan bertanggung jawab untuk mengontrol utas dan untuk beberapa tugas yang harus dilakukan di utas ini. Apa cara yang benar? Jawabannya ditemukan dalam
run()
metode yang sama, yang kami timpa:
public void run() {
if (target != null) {
target.run();
}
}
Di sini,
target
ada beberapa
java.lang.Runnable
, yang bisa kita lewati saat membuat instance dari kelas Thread. Ini berarti kita dapat melakukan ini:
public class HelloWorld{
public static void main(String[] args) {
Runnable task = new Runnable() {
public void run() {
System.out.println("Hello, World!");
}
};
Thread thread = new Thread(task);
thread.start();
}
}
Runnable
juga telah menjadi antarmuka fungsional sejak Java 1.8. Ini memungkinkan untuk menulis kode yang lebih indah untuk tugas utas:
public static void main(String[] args) {
Runnable task = () -> {
System.out.println("Hello, World!");
};
Thread thread = new Thread(task);
thread.start();
}
Kesimpulan
Saya harap diskusi ini menjelaskan apa itu utas, bagaimana utas muncul, dan operasi dasar apa yang dapat dilakukan dengan utas. Di
bagian selanjutnya , kita akan mencoba memahami bagaimana utas berinteraksi satu sama lain dan menjelajahi siklus hidup utas.
Lebih baik bersama: Java dan kelas Thread. Bagian II — Sinkronisasi Lebih baik bersama: Java dan kelas Thread. 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!
GO TO FULL VERSION