Perkenalan
Multithreading telah dibangun di Java sejak awal. Jadi mari kita lihat sekilas apa yang dimaksud dengan multithreading.
Mari kita ambil pelajaran resmi dari Oracle sebagai titik awal: "
Pelajaran: Aplikasi "Halo Dunia!" ". Mari kita ubah sedikit kode aplikasi Hello World kita menjadi berikut ini:
class HelloWorldApp {
public static void main(String[] args) {
System.out.println("Hello, " + args[0]);
}
}
args
adalah larik parameter masukan yang diteruskan saat program dimulai. Mari kita simpan kode ini ke file dengan nama yang sesuai dengan nama kelas dan ekstensinya
.java
. Mari kita kompilasi menggunakan utilitas
javac :
javac HelloWorldApp.java
Setelah itu, panggil kode kita dengan beberapa parameter, misalnya Roger:
java HelloWorldApp Roger
Kode kita sekarang memiliki cacat serius. Jika kita tidak memberikan argumen apa pun (yaitu jalankan saja java HelloWorldApp), kita akan mendapatkan kesalahan:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
at HelloWorldApp.main(HelloWorldApp.java:3)
Pengecualian (yaitu kesalahan) terjadi di thread bernama
main
. Ternyata ada semacam thread di jawa? Di sinilah perjalanan kita dimulai.
Java dan utas
Untuk memahami apa itu thread, Anda perlu memahami bagaimana aplikasi Java diluncurkan. Mari kita ubah kode kita sebagai berikut:
class HelloWorldApp {
public static void main(String[] args) {
while (true) {
}
}
}
Sekarang mari kita kompilasi lagi menggunakan javac. Selanjutnya, untuk kenyamanan, kita akan menjalankan kode Java di jendela terpisah. Di Windows Anda dapat melakukan ini seperti ini:
start java HelloWorldApp
. Sekarang, dengan menggunakan utilitas
jps , mari kita lihat informasi apa yang akan diberitahukan Java kepada kita:
Nomor pertama adalah PID atau ID Proses, pengidentifikasi proses. Apa itu proses?
Процесс — это совокупность codeа и данных, разделяющих общее виртуальное addressное пространство.
Dengan bantuan proses, eksekusi berbagai program diisolasi satu sama lain: setiap aplikasi menggunakan area memorinya sendiri tanpa mengganggu program lain. Saya menyarankan Anda untuk membaca artikel lebih detail: "
https://habr.com/post/164487/ ". Suatu proses tidak bisa ada tanpa thread, jadi jika suatu proses ada, setidaknya ada satu thread di dalamnya. Bagaimana hal ini bisa terjadi di Jawa? Saat kita menjalankan program Java, eksekusinya dimulai dengan file
main
. Kita semacam masuk ke dalam programnya, jadi cara khusus ini
main
disebut entry point, atau "entry point". Metodenya
main
harus selalu
public static void
agar Java Virtual Machine (JVM) bisa mulai mengeksekusi program kita. Lihat "
Mengapa metode utama Java statis? " untuk lebih jelasnya. Ternyata peluncur java (java.exe atau javaw.exe) adalah aplikasi sederhana (aplikasi C sederhana): ia memuat berbagai DLL, yang sebenarnya merupakan JVM. Peluncur Java membuat serangkaian panggilan Java Native Interface (JNI) tertentu. JNI adalah mekanisme yang menjembatani dunia Java Virtual Machine dan dunia C++. Ternyata peluncurnya bukan JVM, melainkan pemuatnya. Ia mengetahui perintah yang benar untuk dijalankan untuk memulai JVM. Tahu cara mengatur semua lingkungan yang diperlukan menggunakan panggilan JNI. Pengorganisasian lingkungan ini juga mencakup pembuatan thread utama, yang biasa disebut
main
. Untuk lebih jelas melihat thread apa yang hidup dalam proses java, kami menggunakan program
jvisualvm , yang disertakan dalam JDK. Mengetahui pid suatu proses, kita dapat membuka data di dalamnya dengan segera:
jvisualvm --openpid айдипроцесса
Menariknya, setiap thread memiliki area tersendiri di memori yang dialokasikan untuk proses tersebut. Struktur memori ini disebut tumpukan. Tumpukan terdiri dari bingkai. Bingkai adalah titik pemanggilan suatu metode, titik eksekusi. Sebuah bingkai juga dapat direpresentasikan sebagai StackTraceElement (lihat Java API untuk
StackTraceElement ). Anda dapat membaca lebih lanjut tentang memori yang dialokasikan untuk setiap thread
di sini . Jika kita melihat
Java API dan mencari kata Thread, kita akan melihat bahwa ada kelas
java.lang.Thread . Kelas inilah yang mewakili aliran di Java, dan dengan inilah kita harus bekerja.
java.lang.Utas
Sebuah thread di Java direpresentasikan sebagai turunan dari kelas
java.lang.Thread
. Perlu segera dipahami bahwa instance kelas Thread di Java bukanlah thread itu sendiri. Ini hanyalah sejenis API untuk thread tingkat rendah yang dikelola oleh JVM dan sistem operasi. Saat kami meluncurkan JVM menggunakan peluncur java, ini membuat thread utama dengan nama
main
dan beberapa thread layanan lainnya. Sebagaimana dinyatakan dalam JavaDoc kelas Thread:
When a Java Virtual Machine starts up, there is usually a single non-daemon thread
Ada 2 jenis thread: daemon dan non-daemon. Utas daemon adalah utas latar belakang (utas layanan) yang melakukan beberapa pekerjaan di latar belakang. Istilah menarik ini mengacu pada “Iblis Maxwell”, yang dapat Anda baca lebih lanjut di artikel Wikipedia tentang “
iblis ”. Sebagaimana dinyatakan dalam dokumentasi, JVM terus menjalankan program (proses) hingga:
- Metode Runtime.exit tidak dipanggil
- Semua thread non-daemon menyelesaikan pekerjaannya (tanpa kesalahan dan dengan pengecualian)
Oleh karena itu, detail penting: utas daemon dapat dihentikan pada perintah apa pun yang dijalankan. Oleh karena itu, integritas data di dalamnya tidak terjamin. Oleh karena itu, thread daemon cocok untuk beberapa tugas layanan. Misalnya saja di Java terdapat thread yang bertanggung jawab untuk memproses metode finalisasi atau thread yang berhubungan dengan Garbage Collector (GC). Setiap utas milik beberapa grup (
ThreadGroup ). Dan kelompok dapat masuk ke dalam satu sama 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 memungkinkan Anda menyederhanakan pengelolaan aliran dan melacaknya. Selain grup, thread memiliki pengendali pengecualiannya sendiri. Mari kita lihat sebuah 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 error yang ditangkap oleh handler. Jika Anda tidak menentukan sendiri handlernya, implementasi handler default akan berfungsi, yang akan menampilkan tumpukan kesalahan di StdError. Anda dapat membaca lebih lanjut di ulasan
http://pro-java.ru/java-dlya-opytnyx/obrabotchik-neperexvachennyx-isklyuchenij-java/ ". Selain itu, utas memiliki prioritas. Anda dapat membaca lebih lanjut tentang prioritas di artikel "
Prioritas Thread Java di Multithreading ".
Membuat utas
Sebagaimana dinyatakan dalam dokumentasi, kami memiliki 2 cara untuk membuat thread. Yang pertama adalah menciptakan ahli waris Anda. 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, tugas diluncurkan dalam metode
run
, dan thread diluncurkan dalam metode
start
. Mereka tidak perlu bingung, karena... jika kita menjalankan metode ini
run
secara langsung, tidak ada thread baru yang akan dimulai. Ini adalah metode
start
yang meminta JVM untuk membuat thread baru. Opsi dengan turunan dari Thread buruk karena kami menyertakan Thread dalam hierarki kelas. Kerugian yang kedua adalah kita mulai melanggar prinsip “Tanggung Jawab Tunggal” SOLID, karena kelas kita secara bersamaan bertanggung jawab untuk mengelola thread dan beberapa tugas yang harus dilakukan di thread ini. Yang mana yang benar? Jawabannya ada pada metode
run
yang kita timpa:
public void run() {
if (target != null) {
target.run();
}
}
Berikut
target
ini beberapa
java.lang.Runnable
, yang bisa kita teruskan ke Thread saat membuat instance kelas. Oleh karena itu, 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();
}
}
Ini juga
Runnable
merupakan antarmuka fungsional sejak Java 1.8. Ini memungkinkan Anda menulis kode tugas untuk thread dengan lebih indah:
public static void main(String []args){
Runnable task = () -> {
System.out.println("Hello, World!");
};
Thread thread = new Thread(task);
thread.start();
}
Total
Jadi, saya berharap dari cerita ini jelas apa itu aliran, bagaimana aliran itu ada, dan operasi dasar apa yang dapat dilakukan dengannya. Pada
bagian selanjutnya , ada baiknya memahami bagaimana thread berinteraksi satu sama lain dan bagaimana siklus hidupnya. #Viacheslav
GO TO FULL VERSION