Panimula
Ang multithreading ay itinayo sa Java mula noong mga unang araw nito. Kaya't tingnan natin kung tungkol saan ang multithreading.
Kunin natin ang opisyal na aral mula sa Oracle bilang panimulang punto: "
Lesson: The "Hello World!" Application ". Baguhin natin ng kaunti ang code ng ating Hello World application sa sumusunod:
class HelloWorldApp {
public static void main(String[] args) {
System.out.println("Hello, " + args[0]);
}
}
args
ay isang hanay ng mga parameter ng input na ipinasa kapag nagsimula ang programa. I-save natin ang code na ito sa isang file na may pangalan na tumutugma sa pangalan ng klase at extension
.java
. Mag-compile tayo gamit ang
javac utility :
javac HelloWorldApp.java
Pagkatapos nito, tawagan ang aming code na may ilang parameter, halimbawa, Roger:
java HelloWorldApp Roger
Ang aming code ay mayroon na ngayong malubhang depekto. Kung hindi kami magpasa ng anumang argumento (i.e. i-execute lang ang java HelloWorldApp), magkakaroon kami ng error:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
at HelloWorldApp.main(HelloWorldApp.java:3)
Isang pagbubukod (ibig sabihin, isang error) ang naganap sa isang thread na pinangalanang
main
. Ito ay lumiliko na mayroong ilang mga uri ng mga thread sa Java? Dito na magsisimula ang ating paglalakbay.
Java at mga thread
Upang maunawaan kung ano ang isang thread, kailangan mong maunawaan kung paano inilunsad ang isang Java application. Baguhin natin ang ating code gaya ng sumusunod:
class HelloWorldApp {
public static void main(String[] args) {
while (true) {
}
}
}
Ngayon ay i-compile natin ito muli gamit ang javac. Susunod, para sa kaginhawahan, patakbuhin namin ang aming Java code sa isang hiwalay na window. Sa Windows maaari mong gawin ito tulad nito:
start java HelloWorldApp
. Ngayon, gamit ang
jps utility , tingnan natin kung anong impormasyon ang sasabihin sa atin ng Java:
Ang unang numero ay ang PID o Process ID, ang process identifier. Ano ang isang proseso?
Процесс — это совокупность codeа и данных, разделяющих общее виртуальное addressное пространство.
Sa tulong ng mga proseso, ang pagpapatupad ng iba't ibang mga programa ay nakahiwalay sa bawat isa: ang bawat aplikasyon ay gumagamit ng sarili nitong lugar ng memorya nang hindi nakakasagabal sa iba pang mga programa. Ipinapayo ko sa iyo na basahin ang artikulo nang mas detalyado: "
https://habr.com/post/164487/ ". Ang isang proseso ay hindi maaaring umiral nang walang mga thread, kaya kung mayroong isang proseso, hindi bababa sa isang thread ang umiiral dito. Paano ito nangyayari sa Java? Kapag nagpatakbo kami ng isang Java program, ang pagpapatupad nito ay magsisimula sa
main
. Uri ng pagpasok namin sa programa, kaya ang espesyal na paraan na ito
main
ay tinatawag na entry point, o "entry point". Ang pamamaraan
main
ay dapat palaging
public static void
upang ang Java Virtual Machine (JVM) ay makapagsimulang isagawa ang aming programa. Tingnan ang "
Bakit static ang pangunahing pamamaraan ng Java? " para sa higit pang mga detalye. Lumalabas na ang java launcher (java.exe o javaw.exe) ay isang simpleng application (simpleng C application): naglo-load ito ng iba't ibang DLL, na talagang JVM. Ang Java launcher ay gumagawa ng isang partikular na hanay ng mga Java Native Interface (JNI) na tawag. Ang JNI ay ang mekanismo na nagtulay sa mundo ng Java Virtual Machine at sa mundo ng C++. Lumalabas na ang launcher ay hindi ang JVM, ngunit ang loader nito. Alam nito ang tamang mga utos na isasagawa upang simulan ang JVM. Alam kung paano ayusin ang lahat ng kinakailangang kapaligiran gamit ang mga tawag sa JNI. Kasama rin sa organisasyong ito ng kapaligiran ang paglikha ng isang pangunahing thread, na karaniwang tinatawag na
main
. Upang mas malinaw na makita kung anong mga thread ang nakatira sa isang proseso ng java, ginagamit namin ang
jvisualvm program , na kasama sa JDK. Alam ang pid ng isang proseso, maaari naming buksan kaagad ang data dito:
jvisualvm --openpid айдипроцесса
Kapansin-pansin, ang bawat thread ay may sariling hiwalay na lugar sa memorya na inilaan para sa proseso. Ang istraktura ng memorya na ito ay tinatawag na stack. Ang isang stack ay binubuo ng mga frame. Ang isang frame ay ang punto ng pagtawag ng isang pamamaraan, punto ng pagpapatupad. Ang isang frame ay maaari ding katawanin bilang isang StackTraceElement (tingnan ang Java API para sa
StackTraceElement ). Maaari kang magbasa nang higit pa tungkol sa memorya na inilaan sa bawat thread
dito . Kung titingnan natin
ang Java API at hahanapin ang salitang Thread, makikita natin na mayroong class na
java.lang.Thread . Ang klase na ito ang kumakatawan sa isang stream sa Java, at kasama nito kailangan nating magtrabaho.
java.lang.Thread
Ang isang thread sa Java ay kinakatawan bilang isang halimbawa ng klase
java.lang.Thread
. Ito ay nagkakahalaga ng pag-unawa kaagad na ang mga pagkakataon ng klase ng Thread sa Java ay hindi mga thread mismo. Isa lang itong uri ng API para sa mga low-level na thread na pinamamahalaan ng JVM at ng operating system. Kapag inilunsad namin ang JVM gamit ang java launcher, lumilikha ito ng pangunahing thread na may pangalan
main
at marami pang mga thread ng serbisyo. Gaya ng nakasaad sa JavaDoc ng klase ng Thread:
When a Java Virtual Machine starts up, there is usually a single non-daemon thread
Mayroong 2 uri ng mga thread: mga daemon at hindi mga daemon. Ang mga daemon thread ay mga background thread (mga service thread) na gumaganap ng ilang trabaho sa background. Ang kawili-wiling terminong ito ay isang sanggunian sa "demonyo ni Maxwell," na maaari mong basahin nang higit pa tungkol sa artikulo sa Wikipedia tungkol sa "
mga demonyo ." Gaya ng nakasaad sa dokumentasyon, patuloy na isinasagawa ng JVM ang programa (proseso) hanggang:
- Ang Runtime.exit na paraan ay hindi tinatawag
- Nakumpleto ng lahat ng mga thread na hindi daemon ang kanilang trabaho (parehong walang mga error at may mga pagbubukod na itinapon)
Kaya ang mahalagang detalye: ang mga daemon thread ay maaaring wakasan sa anumang utos na ipapatupad. Samakatuwid, ang integridad ng data sa kanila ay hindi ginagarantiyahan. Samakatuwid, ang mga daemon thread ay angkop para sa ilang mga gawain sa serbisyo. Halimbawa, sa Java mayroong isang thread na responsable para sa pagproseso ng mga pamamaraan ng pag-finalize o mga thread na nauugnay sa Garbage Collector (GC). Ang bawat thread ay kabilang sa ilang grupo (
ThreadGroup ). At ang mga grupo ay maaaring pumasok sa isa't isa, na bumubuo ng ilang hierarchy o istraktura.
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());
}
Binibigyang-daan ka ng mga grupo na i-streamline ang pamamahala ng mga daloy at subaybayan ang mga ito. Bilang karagdagan sa mga grupo, ang mga thread ay may sariling exception handler. Tingnan natin ang isang halimbawa:
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);
}
Ang paghahati ng zero ay magdudulot ng error na mahuhuli ng handler. Kung hindi mo mismo tinukoy ang handler, gagana ang default na pagpapatupad ng handler, na magpapakita ng error stack sa StdError. Maaari kang magbasa nang higit pa sa pagsusuri
http://pro-java.ru/java-dlya-opytnyx/obrabotchik-neperexvachennyx-isklyuchenij-java/ ". Bilang karagdagan, ang thread ay may priyoridad. Maaari kang magbasa nang higit pa tungkol sa mga priyoridad sa artikulong "
Java Thread Priority sa Multithreading ".
Paggawa ng thread
Gaya ng nakasaad sa dokumentasyon, mayroon kaming 2 paraan para gumawa ng thread. Ang una ay lumikha ng iyong sariling tagapagmana. Halimbawa:
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();
}
}
Tulad ng nakikita mo, ang gawain ay inilunsad sa pamamaraan
run
, at ang thread ay inilunsad sa pamamaraan
start
. Hindi sila dapat malito, dahil... kung tatakbo tayo nang direkta sa pamamaraan
run
, walang bagong thread na magsisimula. Ito ang paraan
start
na humihiling sa JVM na lumikha ng bagong thread. Ang opsyon na may descendant mula sa Thread ay masama dahil isinama namin ang Thread sa hierarchy ng klase. Ang pangalawang kawalan ay nagsisimula na tayong lumabag sa prinsipyo ng "Sole Responsibility" SOLID, dahil ang aming klase ay nagiging sabay-sabay na responsable para sa parehong pamamahala sa thread at para sa ilang gawain na dapat gawin sa thread na ito. Ano ang tama? Ang sagot ay nasa mismong pamamaraan
run
na aming na-override:
public void run() {
if (target != null) {
target.run();
}
}
Narito
target
ang ilang
java.lang.Runnable
, na maaari naming ipasa sa Thread kapag gumagawa ng isang instance ng klase. Samakatuwid, magagawa natin ito:
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();
}
}
Isa rin itong
Runnable
functional na interface mula noong Java 1.8. Nagbibigay-daan ito sa iyo na magsulat ng task code para sa mga thread nang mas maganda:
public static void main(String []args){
Runnable task = () -> {
System.out.println("Hello, World!");
};
Thread thread = new Thread(task);
thread.start();
}
Kabuuan
Kaya, umaasa ako mula sa kuwentong ito ay malinaw kung ano ang isang stream, kung paano sila umiiral at kung anong mga pangunahing operasyon ang maaaring gawin sa kanila. Sa
susunod na bahagi , sulit na maunawaan kung paano nakikipag-ugnayan ang mga thread sa isa't isa at kung ano ang kanilang ikot ng buhay. #Viacheslav
GO TO FULL VERSION