pengenalan

Multithreading telah dibina ke dalam Java sejak awal lagi. Jadi, mari kita lihat secara ringkas perkara yang dipanggil multithreading ini. Lebih baik bersama: Java dan kelas Thread.  Bahagian I — Benang pelaksanaan - 1Kami mengambil pelajaran rasmi daripada Oracle sebagai titik rujukan: " Pelajaran: Aplikasi "Hello World! ". Kami akan mengubah sedikit kod program Hello World kami seperti berikut:

class HelloWorldApp {
    public static void main(String[] args) {
        System.out.println("Hello, " + args[0]);
    }
}
argsialah tatasusunan parameter input yang diluluskan apabila program dimulakan. Simpan kod ini pada fail dengan nama yang sepadan dengan nama kelas dan mempunyai sambungan .java. Susunnya menggunakan utiliti javac : javac HelloWorldApp.java. Kemudian, kami menjalankan kod kami dengan beberapa parameter, contohnya, "Roger": java HelloWorldApp Roger Lebih baik bersama: Java dan kelas Thread.  Bahagian I — Benang pelaksanaan - 2Kod kami pada masa ini mempunyai kecacatan yang serius. Jika anda tidak meluluskan sebarang hujah (iaitu jalankan hanya "java HelloWorldApp"), maka kami mendapat ralat:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
        at HelloWorldApp.main(HelloWorldApp.java:3)
Pengecualian (iaitu ralat) berlaku dalam urutan bernama "utama". Jadi, Java mempunyai benang? Di sinilah bermulanya perjalanan kami.

Java dan benang

Untuk memahami apa itu benang, anda perlu memahami cara program Java bermula. Mari tukar kod kami seperti berikut:

class HelloWorldApp {
    public static void main(String[] args) {
		while (true) { 
			// Do nothing
		}
	}
}
Sekarang mari kita susun semula dengan javac. Untuk kemudahan, kami akan menjalankan kod Java kami dalam tetingkap yang berasingan. Pada Windows, ini boleh dilakukan seperti ini: start java HelloWorldApp. Sekarang kami akan menggunakan utiliti jps untuk melihat maklumat yang Java boleh beritahu kami: Lebih baik bersama: Java dan kelas Thread.  Bahagian I — Benang pelaksanaan - 3Nombor pertama ialah PID atau ID Proses. Apakah proses?

A process is a combination of code and data sharing a common virtual address space.
Dengan proses, atur cara yang berbeza diasingkan antara satu sama lain semasa ia berjalan: setiap aplikasi menggunakan kawasannya sendiri dalam ingatan tanpa mengganggu program lain. Untuk mengetahui lebih lanjut, saya mengesyorkan membaca tutorial ini: Proses dan Benang . Proses tidak boleh wujud tanpa utas, jadi jika proses wujud, maka ia mempunyai sekurang-kurangnya satu utas. Tetapi bagaimana ini berlaku di Jawa? Apabila kita memulakan program Java, pelaksanaan bermula dengan mainkaedah. Seolah-olah kita melangkah ke dalam program, jadi mainkaedah khas ini dipanggil titik masuk. Kaedah mainmesti sentiasa "kosong statik awam", supaya mesin maya Java (JVM) boleh mula melaksanakan program kami. Untuk maklumat lanjut, Mengapakah kaedah utama Java statik?. Ternyata pelancar Java (java.exe atau javaw.exe) ialah aplikasi C yang mudah: ia memuatkan pelbagai DLL yang sebenarnya terdiri daripada JVM. Pelancar Java membuat set panggilan Java Native Interface (JNI) tertentu. JNI ialah mekanisme untuk menghubungkan dunia mesin maya Java dengan dunia C++. Jadi, pelancar bukanlah JVM itu sendiri, melainkan mekanisme untuk memuatkannya. Ia mengetahui arahan yang betul untuk dilaksanakan untuk memulakan JVM. Ia tahu cara menggunakan panggilan JNI untuk menyediakan persekitaran yang diperlukan. Menyediakan persekitaran ini termasuk mencipta utas utama, yang dipanggil "utama", sudah tentu. Untuk menggambarkan dengan lebih baik benang yang wujud dalam proses Java, kami menggunakan jvisualvmalat, yang disertakan dengan JDK. Mengetahui pid proses, kita boleh melihat maklumat tentang proses itu dengan segera: jvisualvm --openpid <process id> Lebih baik bersama: Java dan kelas Thread.  Bahagian I — Benang pelaksanaan - 4Menariknya, setiap thread mempunyai kawasan tersendiri dalam memori yang diperuntukkan kepada proses tersebut. Struktur ingatan ini dipanggil timbunan. Timbunan terdiri daripada bingkai. Bingkai mewakili pengaktifan kaedah (panggilan kaedah yang belum selesai). Bingkai juga boleh diwakili sebagai StackTraceElement (lihat API Java untuk StackTraceElement ). Anda boleh mendapatkan lebih banyak maklumat tentang memori yang diperuntukkan kepada setiap utas dalam perbincangan di sini: " Bagaimana Java (JVM) memperuntukkan tindanan untuk setiap utas ". Jika anda melihat API Java dan mencari perkataan "Thread", anda akan menemui java.lang.Threadkelas. Ini ialah kelas yang mewakili benang dalam Java, dan kami perlu bekerja dengannya. Lebih baik bersama: Java dan kelas Thread.  Bahagian I — Benang pelaksanaan - 5

java.lang.Thread

Di Jawa, benang diwakili oleh contoh kelas java.lang.Thread. Anda harus segera memahami bahawa contoh kelas Thread bukanlah urutan pelaksanaan. Ini hanyalah sejenis API untuk urutan peringkat rendah yang diuruskan oleh JVM dan sistem pengendalian. Apabila kita memulakan JVM menggunakan pelancar Java, ia mencipta mainbenang yang dipanggil "utama" dan beberapa benang pengemasan lain. Seperti yang dinyatakan dalam JavaDoc untuk kelas Thread: When a Java Virtual Machine starts up, there is usually a single non-daemon thread. Terdapat 2 jenis benang: daemon dan bukan daemon. Benang Daemon ialah benang latar belakang (kemas rumah) yang melakukan beberapa kerja di latar belakang. Perkataan "daemon" merujuk kepada syaitan Maxwell. Anda boleh mengetahui lebih lanjut dalam artikel Wikipedia ini . Seperti yang dinyatakan dalam dokumentasi, JVM terus melaksanakan program (proses) sehingga:
  • Kaedah Runtime.exit () dipanggil
  • Semua utas BUKAN daemon menyelesaikan kerja mereka (tanpa ralat atau dengan pengecualian yang dilemparkan)
Perincian penting berikut daripada ini: benang daemon boleh ditamatkan pada bila-bila masa. Akibatnya, tiada jaminan tentang integriti data mereka. Sehubungan itu, benang daemon sesuai untuk tugas pengemasan tertentu. Sebagai contoh, Java mempunyai benang yang bertanggungjawab untuk memproses finalize()panggilan kaedah, iaitu benang yang terlibat dengan Pengumpul Sampah (gc). Setiap utas adalah sebahagian daripada kumpulan ( ThreadGroup ). Dan kumpulan boleh menjadi sebahagian daripada kumpulan 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());
}
Kumpulan membawa susunan kepada pengurusan benang. Selain kumpulan, benang mempunyai pengendali pengecualiannya sendiri. Lihat satu 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);
}
Pembahagian dengan sifar akan menyebabkan ralat yang akan ditangkap oleh pengendali. Jika anda tidak menentukan pengendali anda sendiri, maka JVM akan menggunakan pengendali lalai, yang akan mengeluarkan surih tindanan pengecualian kepada StdError. Setiap benang juga mempunyai keutamaan. Anda boleh membaca lebih lanjut mengenai keutamaan dalam artikel ini: Java Thread Priority dalam Multithreading .

Mencipta benang

Seperti yang dinyatakan dalam dokumentasi, kami mempunyai 2 cara untuk mencipta benang. Cara pertama ialah mencipta subkelas anda sendiri. Sebagai contoh:

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, kerja tugas berlaku dalam run()kaedah, tetapi benang itu sendiri dimulakan dalam start()kaedah. Jangan mengelirukan kaedah ini: jika kita memanggil un()kaedah r secara langsung, maka tiada utas baru akan dimulakan. Ia adalah start()kaedah yang meminta JVM membuat utas baharu. Pilihan yang kami mewarisi Thread ini sudah buruk kerana kami memasukkan Thread dalam hierarki kelas kami. Kelemahan kedua ialah kita mula melanggar prinsip "tanggungjawab tunggal". Iaitu, kelas kami bertanggungjawab pada masa yang sama untuk mengawal utas dan untuk beberapa tugas yang perlu dilakukan dalam utas ini. Apakah cara yang betul? Jawapannya terdapat dalam run()kaedah yang sama, yang kami tolak:

public void run() {
	if (target != null) {
		target.run();
	}
}
Di sini, targetadalah beberapa java.lang.Runnable, yang boleh kita lalui apabila membuat contoh kelas Thread. Ini bermakna kita boleh 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();
    }
}
Runnablejuga telah menjadi antara muka berfungsi sejak Java 1.8. Ini memungkinkan untuk menulis kod yang lebih cantik untuk tugasan benang:

public static void main(String[] args) {
	Runnable task = () -> { 
		System.out.println("Hello, World!");
	};
	Thread thread = new Thread(task);
	thread.start();
}

Kesimpulan

Saya harap perbincangan ini menjelaskan apa itu utas, bagaimana utas itu wujud, dan apakah operasi asas yang boleh dilakukan dengan utas. Dalam bahagian seterusnya , kami akan cuba memahami cara utas berinteraksi antara satu sama lain dan meneroka kitaran hayat utas. Lebih baik bersama: Java dan kelas Thread. Bahagian II — Penyegerakan Lebih baik bersama: Java dan kelas Thread. Bahagian III — Interaksi Lebih Baik bersama: Java dan kelas Thread. Bahagian IV — Boleh Dipanggil, Masa Depan dan rakan Lebih baik bersama: Java dan kelas Thread. Bahagian V — Pelaksana, ThreadPool, Fork/Join Better together: Java dan kelas Thread. Bahagian VI - Jauhkan api!