CodeGym /Blog Java /rawak /Lebih baik bersama: Java dan kelas Thread. Bahagian II — ...
John Squirrels
Tahap
San Francisco

Lebih baik bersama: Java dan kelas Thread. Bahagian II — Penyegerakan

Diterbitkan dalam kumpulan

pengenalan

Jadi, kita tahu bahawa Java mempunyai benang. Anda boleh membaca tentang itu dalam ulasan bertajuk Better together: Java and the Thread class. Bahagian I — Benang pelaksanaan . Benang diperlukan untuk melaksanakan kerja secara selari. Ini menjadikannya sangat berkemungkinan bahawa benang akan berinteraksi antara satu sama lain. Mari kita lihat bagaimana ini berlaku dan apakah alat asas yang kita ada. Lebih baik bersama: Java dan kelas Thread.  Bahagian II — Penyegerakan - 1

hasil

Thread.yield() membingungkan dan jarang digunakan. Ia diterangkan dalam pelbagai cara di Internet. Termasuk beberapa orang yang menulis bahawa terdapat beberapa baris gilir benang, di mana urutan akan turun berdasarkan keutamaan utas. Orang lain menulis bahawa thread akan menukar statusnya daripada "Running" kepada "Runnable" (walaupun tiada perbezaan antara status ini, iaitu Java tidak membezakan antara mereka). Realitinya adalah bahawa semuanya kurang terkenal tetapi lebih mudah dari segi erti kata. Lebih baik bersama: Java dan kelas Thread.  Bahagian II — Penyegerakan - 2Terdapat pepijat ( JDK-6416721: (spec thread) Fix Thread.yield() javadoc ) dilog untuk yield()dokumentasi kaedah. Jika anda membacanya, jelas bahawayield()kaedah sebenarnya hanya memberikan beberapa cadangan kepada penjadual benang Java bahawa benang ini boleh diberi masa pelaksanaan yang lebih sedikit. Tetapi apa yang sebenarnya berlaku, iaitu sama ada penjadual bertindak mengikut pengesyoran dan perkara yang dilakukan secara umum, bergantung pada pelaksanaan JVM dan sistem pengendalian. Dan ia mungkin bergantung pada beberapa faktor lain juga. Semua kekeliruan berkemungkinan besar disebabkan oleh fakta bahawa multithreading telah difikirkan semula apabila bahasa Java telah berkembang. Baca lebih lanjut dalam gambaran keseluruhan di sini: Pengenalan Ringkas kepada Java Thread.yield() .

tidur

Seutas benang boleh tidur semasa pelaksanaannya. Ini adalah jenis interaksi yang paling mudah dengan utas lain. Sistem pengendalian yang menjalankan mesin maya Java yang menjalankan kod Java kami mempunyai penjadual benangnya sendiri . Ia menentukan utas mana untuk dimulakan dan bila. Pengaturcara tidak boleh berinteraksi dengan penjadual ini terus daripada kod Java, hanya melalui JVM. Dia boleh meminta penjadual untuk menjeda benang untuk seketika, iaitu untuk meletakkannya tidur. Anda boleh membaca lebih lanjut dalam artikel ini: Thread.sleep() dan Cara Multithreading berfungsi . Anda juga boleh menyemak cara utas berfungsi dalam sistem pengendalian Windows: Dalaman Windows Thread . Dan sekarang mari kita lihat dengan mata kepala kita sendiri. Simpan kod berikut dalam fail bernama HelloWorldApp.java:

class HelloWorldApp {
    public static void main(String []args) {
        Runnable task = () -> {
            try {
                int secToWait = 1000 * 60;
                Thread.currentThread().sleep(secToWait);
                System.out.println("Woke up");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(task);
        thread.start();
    }
}
Seperti yang anda lihat, kami mempunyai beberapa tugas yang menunggu selama 60 saat, selepas itu program tamat. Kami menyusun menggunakan arahan " javac HelloWorldApp.java" dan kemudian menjalankan program menggunakan " java HelloWorldApp". Adalah lebih baik untuk memulakan program dalam tetingkap yang berasingan. Sebagai contoh, pada Windows, ia adalah seperti ini: start java HelloWorldApp. Kami menggunakan arahan jps untuk mendapatkan PID (ID proses), dan kami membuka senarai utas dengan " jvisualvm --openpid pid: Lebih baik bersama: Java dan kelas Thread.  Bahagian II — Penyegerakan - 3Seperti yang anda lihat, utas kami kini mempunyai status "Tidur". Malah, terdapat cara yang lebih elegan untuk membantu benang kami mempunyai mimpi indah:

try {
	TimeUnit.SECONDS.sleep(60);
	System.out.println("Woke up");
} catch (InterruptedException e) {
	e.printStackTrace();
}
Adakah anda perasan bahawa kami mengendalikan InterruptedExceptiondi mana-mana? Mari kita fahami mengapa.

Thread.interrupt()

Masalahnya ialah semasa benang sedang menunggu/tidur, seseorang mungkin mahu mengganggu. Dalam kes ini, kami mengendalikan InterruptedException. Mekanisme ini diwujudkan selepas Thread.stop()kaedah itu diisytiharkan Dihentikan, iaitu ketinggalan zaman dan tidak diingini. Sebabnya ialah apabila stop()kaedah itu dipanggil, benang itu hanya "dibunuh", yang sangat tidak dapat diramalkan. Kami tidak dapat mengetahui bila urutan akan dihentikan, dan kami tidak dapat menjamin konsistensi data. Bayangkan anda sedang menulis data ke fail semasa benang dimatikan. Daripada membunuh benang, pencipta Java memutuskan bahawa adalah lebih logik untuk memberitahunya bahawa ia harus diganggu. Cara untuk bertindak balas kepada maklumat ini adalah perkara yang harus diputuskan oleh benang itu sendiri. Untuk butiran lanjut, baca Mengapa Thread.stop ditamatkan?di laman web Oracle. Mari lihat contoh:

public static void main(String []args) {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(60);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
Dalam contoh ini, kita tidak akan menunggu 60 saat. Sebaliknya, kami akan segera memaparkan "Terganggu". Ini kerana kami memanggil interrupt()kaedah pada benang. Kaedah ini menetapkan bendera dalaman yang dipanggil "status gangguan". Iaitu, setiap utas mempunyai bendera dalaman yang tidak boleh diakses secara langsung. Tetapi kami mempunyai kaedah asli untuk berinteraksi dengan bendera ini. Tetapi itu bukan satu-satunya cara. Utas mungkin sedang berjalan, tidak menunggu sesuatu, hanya melakukan tindakan. Tetapi ia mungkin menjangkakan bahawa orang lain akan mahu menamatkan kerjanya pada masa tertentu. Sebagai contoh:

public static void main(String []args) {
	Runnable task = () -> {
		while(!Thread.currentThread().isInterrupted()) {
			// Do some work
		}
		System.out.println("Finished");
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
Dalam contoh di atas, whilegelung akan dilaksanakan sehingga benang diganggu secara luaran. Bagi isInterruptedbendera, adalah penting untuk mengetahui bahawa jika kita menangkap InterruptedException, bendera isInterrupted akan ditetapkan semula, dan kemudian isInterrupted()akan kembali palsu. Kelas Thread juga mempunyai kaedah Thread.interrupted() statik yang digunakan hanya pada utas semasa, tetapi kaedah ini menetapkan semula bendera kepada false! Baca lebih lanjut dalam bab ini bertajuk Gangguan Benang .

Sertai (Tunggu thread lain selesai)

Jenis menunggu yang paling mudah ialah menunggu thread lain untuk selesai.

public static void main(String []args) throws InterruptedException {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(5);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.join();
	System.out.println("Finished");
}
Dalam contoh ini, urutan baharu akan tidur 5 saat. Pada masa yang sama, benang utama akan menunggu sehingga benang tidur bangun dan menyelesaikan kerjanya. Jika anda melihat keadaan benang dalam JVisualVM, maka ia akan kelihatan seperti ini: Lebih baik bersama: Java dan kelas Thread.  Bahagian II — Penyegerakan - 4Terima kasih kepada alat pemantauan, anda boleh melihat apa yang sedang berlaku dengan benang. Kaedah ini joinagak mudah, kerana ia hanyalah kaedah dengan kod Java yang dilaksanakan wait()selagi benang yang dipanggil masih hidup. Sebaik sahaja benang mati (apabila ia selesai dengan kerjanya), penantian terganggu. Dan itu semua keajaiban kaedah join(). Jadi, mari kita beralih kepada perkara yang paling menarik.

Pantau

Multithreading merangkumi konsep monitor. Perkataan monitor datang ke bahasa Inggeris melalui bahasa Latin abad ke-16 dan bermaksud "alat atau peranti yang digunakan untuk memerhati, menyemak atau menyimpan rekod proses yang berterusan". Dalam konteks artikel ini, kami akan cuba membincangkan perkara asas. Bagi sesiapa yang mahukan butiran, sila selami bahan yang dipautkan. Kami memulakan perjalanan kami dengan Spesifikasi Bahasa Java (JLS): 17.1. Penyegerakan . Ia mengatakan perkara berikut: Lebih baik bersama: Java dan kelas Thread.  Bahagian II — Penyegerakan - 5Ternyata Java menggunakan mekanisme "monitor" untuk penyegerakan antara benang. Monitor dikaitkan dengan setiap objek, dan benang boleh memperolehnya dengan lock()atau melepaskannya dengan unlock(). Seterusnya, kami akan menemui tutorial di laman web Oracle: Kunci Intrinsik dan Penyegerakan. Tutorial ini mengatakan bahawa penyegerakan Java dibina di sekeliling entiti dalaman yang dipanggil kunci intrinsik atau kunci monitor . Kunci ini selalunya dipanggil " monitor ". Kami juga melihat sekali lagi bahawa setiap objek di Jawa mempunyai kunci intrinsik yang dikaitkan dengannya. Anda boleh membaca Java - Kunci Intrinsik dan Penyegerakan . Seterusnya adalah penting untuk memahami bagaimana objek dalam Java boleh dikaitkan dengan monitor. Di Java, setiap objek mempunyai pengepala yang menyimpan metadata dalaman yang tidak tersedia kepada pengaturcara daripada kod, tetapi yang mesin maya perlu berfungsi dengan betul dengan objek. Pengepala objek termasuk "kata tanda", yang kelihatan seperti ini: Lebih baik bersama: Java dan kelas Thread.  Bahagian II — Penyegerakan - 6

https://edu.netbeans.org/contrib/slides/java-overview-and-java-se6.pdf

Berikut ialah artikel JavaWorld yang sangat berguna: Bagaimana mesin maya Java melaksanakan penyegerakan benang . Artikel ini harus digabungkan dengan penerangan daripada bahagian "Ringkasan" isu berikut dalam sistem penjejakan pepijat JDK: JDK-8183909 . Anda boleh membaca perkara yang sama di sini: JEP-8183909 . Jadi, di Java, monitor dikaitkan dengan objek dan digunakan untuk menyekat benang apabila benang cuba memperoleh (atau mendapatkan) kunci. Berikut ialah contoh paling mudah:

public class HelloWorld{
    public static void main(String []args){
        Object object = new Object();
        synchronized(object) {
            System.out.println("Hello World");
        }
    }
}
Di sini, utas semasa (yang mana baris kod ini dilaksanakan) menggunakan synchronizedkata kunci untuk cuba menggunakan monitor yang dikaitkan denganobject"\pembolehubah untuk mendapatkan/memperoleh kunci. Jika tiada orang lain yang bertanding untuk monitor (iaitu tiada orang lain yang menjalankan kod disegerakkan menggunakan objek yang sama), maka Java boleh cuba melakukan pengoptimuman yang dipanggil "penguncian berat sebelah". Teg yang berkaitan dan rekod tentang benang yang memiliki kunci monitor ditambahkan pada perkataan tanda dalam pengepala objek. Ini mengurangkan overhed yang diperlukan untuk mengunci monitor. Jika monitor sebelum ini dimiliki oleh benang lain, maka penguncian sedemikian tidak mencukupi. JVM bertukar kepada jenis penguncian seterusnya: "penguncian asas". Ia menggunakan operasi bandingkan-dan-tukar (CAS). Lebih-lebih lagi, kata tanda pengepala objek itu sendiri tidak lagi menyimpan perkataan tanda, sebaliknya merujuk kepada tempat ia disimpan, dan teg berubah supaya JVM memahami bahawa kami menggunakan penguncian asas. Jika berbilang benang bersaing (bertanding) untuk monitor (satu telah memperoleh kunci, dan satu saat sedang menunggu kunci dilepaskan), maka teg dalam perkataan tanda berubah, dan perkataan tanda kini menyimpan rujukan kepada monitor sebagai objek — beberapa entiti dalaman JVM. Seperti yang dinyatakan dalam JDK Enchancement Proposal (JEP), situasi ini memerlukan ruang dalam kawasan Native Heap memori untuk menyimpan entiti ini. Rujukan kepada lokasi memori entiti dalaman ini akan disimpan dalam kata tanda pengepala objek. Oleh itu, monitor adalah benar-benar mekanisme untuk menyegerakkan akses kepada sumber yang dikongsi antara berbilang benang. JVM bertukar antara beberapa pelaksanaan mekanisme ini. Jadi, untuk kesederhanaan, apabila bercakap tentang monitor, kita sebenarnya bercakap tentang kunci. dan satu saat sedang menunggu kunci dilepaskan), kemudian teg dalam perkataan tanda berubah, dan perkataan tanda kini menyimpan rujukan kepada monitor sebagai objek — beberapa entiti dalaman JVM. Seperti yang dinyatakan dalam JDK Enchancement Proposal (JEP), situasi ini memerlukan ruang dalam kawasan Native Heap memori untuk menyimpan entiti ini. Rujukan kepada lokasi memori entiti dalaman ini akan disimpan dalam kata tanda pengepala objek. Oleh itu, monitor adalah benar-benar mekanisme untuk menyegerakkan akses kepada sumber yang dikongsi antara berbilang benang. JVM bertukar antara beberapa pelaksanaan mekanisme ini. Jadi, untuk kesederhanaan, apabila bercakap tentang monitor, kita sebenarnya bercakap tentang kunci. dan satu saat sedang menunggu kunci dilepaskan), kemudian teg dalam perkataan tanda berubah, dan perkataan tanda kini menyimpan rujukan kepada monitor sebagai objek — beberapa entiti dalaman JVM. Seperti yang dinyatakan dalam JDK Enchancement Proposal (JEP), situasi ini memerlukan ruang dalam kawasan Native Heap memori untuk menyimpan entiti ini. Rujukan kepada lokasi memori entiti dalaman ini akan disimpan dalam kata tanda pengepala objek. Oleh itu, monitor adalah benar-benar mekanisme untuk menyegerakkan akses kepada sumber yang dikongsi antara berbilang benang. JVM bertukar antara beberapa pelaksanaan mekanisme ini. Jadi, untuk kesederhanaan, apabila bercakap tentang monitor, kita sebenarnya bercakap tentang kunci. dan perkataan tanda kini menyimpan rujukan kepada monitor sebagai objek — beberapa entiti dalaman JVM. Seperti yang dinyatakan dalam JDK Enchancement Proposal (JEP), situasi ini memerlukan ruang dalam kawasan Native Heap memori untuk menyimpan entiti ini. Rujukan kepada lokasi memori entiti dalaman ini akan disimpan dalam kata tanda pengepala objek. Oleh itu, monitor adalah benar-benar mekanisme untuk menyegerakkan akses kepada sumber yang dikongsi antara berbilang benang. JVM bertukar antara beberapa pelaksanaan mekanisme ini. Jadi, untuk kesederhanaan, apabila bercakap tentang monitor, kita sebenarnya bercakap tentang kunci. dan perkataan tanda kini menyimpan rujukan kepada monitor sebagai objek — beberapa entiti dalaman JVM. Seperti yang dinyatakan dalam JDK Enchancement Proposal (JEP), situasi ini memerlukan ruang dalam kawasan Native Heap memori untuk menyimpan entiti ini. Rujukan kepada lokasi memori entiti dalaman ini akan disimpan dalam kata tanda pengepala objek. Oleh itu, monitor adalah benar-benar mekanisme untuk menyegerakkan akses kepada sumber yang dikongsi antara berbilang benang. JVM bertukar antara beberapa pelaksanaan mekanisme ini. Jadi, untuk kesederhanaan, apabila bercakap tentang monitor, kita sebenarnya bercakap tentang kunci. Rujukan kepada lokasi memori entiti dalaman ini akan disimpan dalam kata tanda pengepala objek. Oleh itu, monitor adalah benar-benar mekanisme untuk menyegerakkan akses kepada sumber yang dikongsi antara berbilang benang. JVM bertukar antara beberapa pelaksanaan mekanisme ini. Jadi, untuk kesederhanaan, apabila bercakap tentang monitor, kita sebenarnya bercakap tentang kunci. Rujukan kepada lokasi memori entiti dalaman ini akan disimpan dalam kata tanda pengepala objek. Oleh itu, monitor adalah benar-benar mekanisme untuk menyegerakkan akses kepada sumber yang dikongsi antara berbilang benang. JVM bertukar antara beberapa pelaksanaan mekanisme ini. Jadi, untuk kesederhanaan, apabila bercakap tentang monitor, kita sebenarnya bercakap tentang kunci. Lebih baik bersama: Java dan kelas Thread.  Bahagian II — Penyegerakan - 7

Disegerakkan (menunggu kunci)

Seperti yang kita lihat sebelum ini, konsep "blok disegerakkan" (atau "bahagian kritikal") berkait rapat dengan konsep monitor. Lihat satu contoh:

public static void main(String[] args) throws InterruptedException {
	Object lock = new Object();

	Runnable task = () -> {
		synchronized(lock) {
			System.out.println("thread");
		}
	};

	Thread th1 = new Thread(task);
	th1.start();
	synchronized(lock) {
		for (int i = 0; i < 8; i++) {
			Thread.currentThread().sleep(1000);
			System.out.print(" " + i);
		}
		System.out.println(" ...");
	}
}
Di sini, utas utama mula-mula menghantar objek tugas ke utas baru, dan kemudian segera memperoleh kunci dan melakukan operasi yang panjang dengannya (8 saat). Selama ini, tugas itu tidak dapat diteruskan, kerana ia tidak boleh memasuki synchronizedblok, kerana kunci sudah diperoleh. Jika benang tidak mendapat kunci, ia akan menunggu monitor. Sebaik sahaja ia mendapat kunci, ia akan meneruskan pelaksanaan. Apabila benang keluar dari monitor, ia melepaskan kunci. Dalam JVisualVM, ia kelihatan seperti ini: Lebih baik bersama: Java dan kelas Thread.  Bahagian II — Penyegerakan - 8Seperti yang anda lihat dalam JVisualVM, statusnya ialah "Monitor", yang bermaksud bahawa utas itu disekat dan tidak boleh mengambil monitor. Anda juga boleh menggunakan kod untuk menentukan status urutan, tetapi nama status yang ditentukan dengan cara ini tidak sepadan dengan nama yang digunakan dalam JVisualVM, walaupun ia serupa. Dalam kes ini,th1.getState()pernyataan dalam gelung for akan mengembalikan BLOCKED , kerana selagi gelung berjalan, lockmonitor objek diduduki oleh mainbenang, dan th1benang disekat dan tidak boleh diteruskan sehingga kunci dilepaskan. Selain blok disegerakkan, keseluruhan kaedah boleh disegerakkan. Sebagai contoh, berikut ialah kaedah daripada HashTablekelas:

public synchronized int size() {
	return count;
}
Kaedah ini akan dilaksanakan oleh hanya satu utas pada bila-bila masa. Adakah kita benar-benar memerlukan kunci? Ya, kami memerlukannya. Dalam kes kaedah contoh, objek "ini" (objek semasa) bertindak sebagai kunci. Terdapat perbincangan menarik mengenai topik ini di sini: Adakah terdapat kelebihan untuk menggunakan Kaedah Disegerakkan dan bukannya Blok Disegerakkan? . Jika kaedah statik, maka kunci tidak akan menjadi objek "ini" (kerana tiada objek "ini" untuk kaedah statik), sebaliknya objek Kelas (contohnya, ) Integer.class.

Tunggu (menunggu monitor). kaedah notify() dan notifyAll().

Kelas Thread mempunyai kaedah menunggu lain yang dikaitkan dengan monitor. Tidak seperti sleep()dan join(), kaedah ini tidak boleh dipanggil begitu sahaja. Namanya ialah wait(). Kaedah ini waitdipanggil pada objek yang dikaitkan dengan monitor yang kita mahu tunggu. Mari lihat contoh:

public static void main(String []args) throws InterruptedException {
	    Object lock = new Object();
	    // The task object will wait until it is notified via lock
	    Runnable task = () -> {
	        synchronized(lock) {
	            try {
	                lock.wait();
	            } catch(InterruptedException e) {
	                System.out.println("interrupted");
	            }
	        }
	        // After we are notified, we will wait until we can acquire the lock
	        System.out.println("thread");
	    };
	    Thread taskThread = new Thread(task);
	    taskThread.start();
        // We sleep. Then we acquire the lock, notify, and release the lock
	    Thread.currentThread().sleep(3000);
	    System.out.println("main");
	    synchronized(lock) {
	        lock.notify();
	    }
}
Dalam JVisualVM, ia kelihatan seperti ini: Lebih baik bersama: Java dan kelas Thread.  Bahagian II — Penyegerakan - 10Untuk memahami cara ini berfungsi, ingat bahawa kaedah wait()dan notify()dikaitkan dengan java.lang.Object. Ia mungkin kelihatan pelik bahawa kaedah berkaitan benang ada di dalam Objectkelas. Tetapi sebab untuk itu kini terbongkar. Anda akan ingat bahawa setiap objek dalam Java mempunyai pengepala. Pengepala mengandungi pelbagai maklumat pengemasan, termasuk maklumat mengenai monitor, iaitu status kunci. Ingat, setiap objek, atau contoh kelas, dikaitkan dengan entiti dalaman dalam JVM, dipanggil kunci atau monitor intrinsik. Dalam contoh di atas, kod untuk objek tugasan menunjukkan bahawa kita memasuki blok disegerakkan untuk monitor yang dikaitkan dengan lockobjek tersebut. Jika kita berjaya memperoleh kunci untuk monitor ini, makawait()dipanggil. Benang yang melaksanakan tugas akan melepaskan lockmonitor objek, tetapi akan memasuki baris gilir benang menunggu pemberitahuan daripada lockmonitor objek. Barisan urutan ini dipanggil SET TUNGGU, yang lebih menggambarkan tujuannya dengan lebih tepat. Iaitu, ia lebih kepada satu set daripada baris gilir. Benang itu mainmencipta urutan baharu dengan objek tugasan, memulakannya dan menunggu 3 saat. Ini menjadikan besar kemungkinan bahawa utas baharu akan dapat memperoleh kunci sebelum utas maindan masuk ke baris gilir monitor. Selepas itu, mainbenang itu sendiri memasuki lockblok disegerakkan objek dan melakukan pemberitahuan benang menggunakan monitor. Selepas pemberitahuan dihantar, mainbenang mengeluarkanlockmonitor objek, dan utas baharu, yang sebelum ini menunggu lockmonitor objek dikeluarkan, meneruskan pelaksanaan. Adalah mungkin untuk menghantar pemberitahuan kepada hanya satu utas ( notify()) atau serentak kepada semua utas dalam baris gilir ( notifyAll()). Baca lebih lanjut di sini: Perbezaan antara notify() dan notifyAll() dalam Java . Adalah penting untuk ambil perhatian bahawa susunan pemberitahuan bergantung pada cara JVM dilaksanakan. Baca lebih lanjut di sini: Bagaimana untuk menyelesaikan kebuluran dengan notify dan notifyAll? . Penyegerakan boleh dilakukan tanpa menentukan objek. Anda boleh melakukan ini apabila keseluruhan kaedah disegerakkan dan bukannya satu blok kod. Sebagai contoh, untuk kaedah statik, kunci akan menjadi objek Kelas (diperolehi melalui .class):

public static synchronized void printA() {
	System.out.println("A");
}
public static void printB() {
	synchronized(HelloWorld.class) {
		System.out.println("B");
	}
}
Dari segi penggunaan kunci, kedua-dua kaedah adalah sama. Jika kaedah tidak statik, maka penyegerakan akan dilakukan menggunakan arus instance, iaitu menggunakan this. By the way, kami katakan tadi anda boleh menggunakan getState()kaedah untuk mendapatkan status thread. Sebagai contoh, untuk urutan dalam baris gilir menunggu monitor, status akan MENUNGGU atau TIMED_WAITING, jika kaedah wait()menyatakan tamat masa. Lebih baik bersama: Java dan kelas Thread.  Bahagian II — Penyegerakan - 11

https://stackoverflow.com/questions/36425942/what-is-the-lifecycle-of-thread-in-java

Kitaran hayat benang

Sepanjang hayatnya, status benang berubah. Malah, perubahan ini merangkumi kitaran hayat benang. Sebaik sahaja urutan dibuat, statusnya adalah BARU. Dalam keadaan ini, benang baharu belum lagi berjalan dan penjadual benang Java belum mengetahui apa-apa tentangnya. Agar penjadual benang mengetahui tentang benang, anda mesti memanggil thread.start()kaedah tersebut. Kemudian benang akan beralih ke keadaan RUNNABLE. Internet mempunyai banyak gambar rajah yang salah yang membezakan antara keadaan "Boleh Dijalankan" dan "Berjalan". Tetapi ini adalah kesilapan, kerana Java tidak membezakan antara "bersedia untuk bekerja" (boleh dijalankan) dan "bekerja" (berjalan). Apabila benang hidup tetapi tidak aktif (tidak Boleh Dijalankan), ia berada dalam salah satu daripada dua keadaan:
  • DISEKAT — menunggu untuk memasuki bahagian kritikal, iaitu synchronizedblok.
  • MENUNGGU — menunggu urutan lain untuk memenuhi beberapa syarat.
Jika syaratnya berpuas hati, maka penjadual benang memulakan benang. Jika urutan menunggu sehingga masa yang ditentukan, maka statusnya ialah TIMED_WAITING. Jika utas tidak lagi berjalan (ia telah selesai atau pengecualian telah dilemparkan), maka ia memasuki status TERMINATED. Untuk mengetahui keadaan benang, gunakan getState()kaedah. Benang juga mempunyai isAlive()kaedah, yang mengembalikan benar jika utas tidak DITAMATKAN.

LockSokongan dan letak benang

Bermula dengan Java 1.6, mekanisme menarik yang dipanggil LockSupport muncul. Lebih baik bersama: Java dan kelas Thread.  Bahagian II — Penyegerakan - 12Kelas ini mengaitkan "permit" dengan setiap urutan yang menggunakannya. Panggilan kepada park()kaedah itu kembali serta-merta jika permit tersedia, menggunakan permit dalam proses. Jika tidak, ia menghalang. Memanggil unparkkaedah menjadikan permit tersedia jika ia belum tersedia. Hanya ada 1 permit. Dokumentasi Java untuk LockSupportmerujuk kepada Semaphorekelas. Mari lihat contoh mudah:

import java.util.concurrent.Semaphore;
public class HelloWorldApp{
    
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(0);
        try {
            semaphore.acquire();
        } catch (InterruptedException e) {
            // Request the permit and wait until we get it
            e.printStackTrace();
        }
        System.out.println("Hello, World!");
    }
}
Kod ini akan sentiasa menunggu, kerana sekarang semaphore mempunyai 0 permit. Dan apabila acquire()dipanggil dalam kod (iaitu meminta permit), benang menunggu sehingga ia menerima permit. Memandangkan kita sedang menunggu, kita mesti mengendalikan InterruptedException. Menariknya, semaphore mendapat keadaan benang yang berasingan. Jika kita melihat dalam JVisualVM, kita akan melihat bahawa negeri itu bukan "Tunggu", tetapi "Park". Lebih baik bersama: Java dan kelas Thread.  Bahagian II — Penyegerakan - 13Mari lihat contoh lain:

public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            // Park the current thread
            System.err.println("Will be Parked");
            LockSupport.park();
            // As soon as we are unparked, we will start to act
            System.err.println("Unparked");
        };
        Thread th = new Thread(task);
        th.start();
        Thread.currentThread().sleep(2000);
        System.err.println("Thread state: " + th.getState());
        
        LockSupport.unpark(th);
        Thread.currentThread().sleep(2000);
}
Status urutan akan MENUNGGU, tetapi JVisualVM membezakan antara waitkata synchronizedkunci dan parkdari LockSupportkelas. Mengapa ini LockSupportsangat penting? Kami beralih semula ke dokumentasi Java dan melihat keadaan benang MENUNGGU . Seperti yang anda lihat, terdapat hanya tiga cara untuk masuk ke dalamnya. Dua daripada cara tersebut ialah wait()dan join(). Dan yang ketiga ialah LockSupport. Di Java, kunci juga boleh dibina di atas LockSupport dan menawarkan alat peringkat lebih tinggi. Jom cuba guna satu. Sebagai contoh, lihat ReentrantLock:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class HelloWorld{

    public static void main(String []args) throws InterruptedException {
        Lock lock = new ReentrantLock();
        Runnable task = () -> {
            lock.lock();
            System.out.println("Thread");
            lock.unlock();
        };
        lock.lock();

        Thread th = new Thread(task);
        th.start();
        System.out.println("main");
        Thread.currentThread().sleep(2000);
        lock.unlock();
    }
}
Sama seperti dalam contoh sebelumnya, semuanya mudah di sini. Objek lockmenunggu seseorang untuk mengeluarkan sumber yang dikongsi. Jika kita melihat dalam JVisualVM, kita akan melihat bahawa utas baharu akan diletakkan sehingga mainutas itu melepaskan kunci kepadanya. Anda boleh membaca lebih lanjut mengenai kunci di sini: Java 8 StampedLocks lwn. ReadWriteLocks dan API Disegerakkan dan Kunci dalam Java. Untuk lebih memahami cara kunci dilaksanakan, anda boleh membaca tentang Phaser dalam artikel ini: Panduan kepada Java Phaser . Dan bercakap tentang pelbagai penyegerak, anda mesti membaca artikel DZone tentang The Java Synchronizers.

Kesimpulan

Dalam ulasan ini, kami meneliti cara utama benang berinteraksi dalam Java. Bahan tambahan: Lebih baik bersama: Java dan kelas Thread. Bahagian I — Benang pelaksanaan Lebih baik bersama: Java dan kelas Benang. 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!
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION