Introduksjon
Multithreading ble bygget inn i Java helt fra begynnelsen. Så, la oss kort se på dette som kalles multithreading.
Vi tar den offisielle leksjonen fra Oracle som et referansepunkt: "
Leksjon: "Hello World!"-applikasjonen ". Vi vil endre koden til Hello World-programmet vårt som følger:
class HelloWorldApp {
public static void main(String[] args) {
System.out.println("Hello, " + args[0]);
}
}
args
er en rekke inndataparametere som sendes når programmet startes. Lagre denne koden i en fil med et navn som samsvarer med klassenavnet og har filtypen
.java
. Kompiler den ved å bruke
javac- verktøyet:
javac HelloWorldApp.java
. Deretter kjører vi koden vår med en eller annen parameter, for eksempel "Roger":
java HelloWorldApp Roger
Koden vår har for øyeblikket en alvorlig feil. Hvis du ikke sender noen argumenter (dvs. kjører bare "java HelloWorldApp"), får vi en feilmelding:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
at HelloWorldApp.main(HelloWorldApp.java:3)
Et unntak (dvs. en feil) oppstod i tråden med navnet "main". Så, Java har tråder? Det er her reisen vår begynner.
Java og tråder
For å forstå hva en tråd er, må du forstå hvordan et Java-program starter. La oss endre koden vår som følger:
class HelloWorldApp {
public static void main(String[] args) {
while (true) {
// Do nothing
}
}
}
La oss nå kompilere den igjen med
javac
. For enkelhets skyld kjører vi Java-koden vår i et eget vindu. På Windows kan dette gjøres slik:
start java HelloWorldApp
. Nå skal vi bruke
jps- verktøyet for å se hvilken informasjon Java kan fortelle oss:
Det første tallet er PID eller prosess-ID. Hva er en prosess?
A process is a combination of code and data sharing a common virtual address space.
Med prosesser er forskjellige programmer isolert fra hverandre mens de kjører: hver applikasjon bruker sitt eget område i minnet uten å forstyrre andre programmer. For å lære mer, anbefaler jeg å lese denne opplæringen:
Prosesser og tråder . En prosess kan ikke eksistere uten en tråd, så hvis en prosess eksisterer, så har den minst én tråd. Men hvordan kommer dette til i Java? Når vi starter et Java-program, begynner kjøringen med
main
metoden. Det er som om vi går inn i programmet, så denne spesielle
main
metoden kalles inngangspunktet. Metoden
main
må alltid være "public static void", slik at Java Virtual Machine (JVM) kan begynne å kjøre programmet vårt. For mer informasjon,
Hvorfor er Java-hovedmetoden statisk?. Det viser seg at Java-starteren (java.exe eller javaw.exe) er en enkel C-applikasjon: den laster de forskjellige DLL-ene som faktisk utgjør JVM. Java-starteren foretar et spesifikt sett med Java Native Interface (JNI)-anrop. JNI er en mekanisme for å koble den virtuelle Java-maskinens verden med verden av C++. Så lanseringen er ikke selve JVM, men snarere en mekanisme for å laste den. Den vet de riktige kommandoene som skal utføres for å starte JVM. Den vet hvordan den skal bruke JNI-anrop til å sette opp det nødvendige miljøet. Å sette opp dette miljøet inkluderer å lage hovedtråden, som selvfølgelig kalles "hoved". For bedre å illustrere hvilke tråder som finnes i en Java-prosess, bruker vi
jvisualvmverktøyet, som følger med JDK. Når vi kjenner pid-en til en prosess, kan vi umiddelbart se informasjon om den prosessen:
jvisualvm --openpid <process id>
Interessant nok har hver tråd sitt eget separate område i minnet som er allokert til prosessen. Denne minnestrukturen kalles en stabel. En stabel består av rammer. En ramme representerer aktiveringen av en metode (et uferdig metodekall). En ramme kan også representeres som et StackTraceElement (se Java API for
StackTraceElement ). Du kan finne mer informasjon om minnet som er tildelt hver tråd i diskusjonen her: "
Hvordan tildeler Java (JVM) stack for hver tråd ". Hvis du ser på
Java API og søker etter ordet "Thread", finner du
java.lang.Threadklasse. Dette er klassen som representerer en tråd i Java, og vi må jobbe med den.
java.lang.Thread
I Java er en tråd representert av en forekomst av
java.lang.Thread
klassen. Du bør umiddelbart forstå at forekomster av Thread-klassen ikke i seg selv er utførelsestråder. Dette er bare en slags API for trådene på lavt nivå som administreres av JVM og operativsystemet. Når vi starter JVM ved å bruke Java-starteren, oppretter den en
main
tråd som kalles "main" og noen få andre husholdningstråder. Som angitt i JavaDoc for Thread-klassen:
When a Java Virtual Machine starts up, there is usually a single non-daemon thread
. Det er 2 typer tråder: demoner og ikke-demoner. Daemon-tråder er bakgrunnstråder (husholdning) som gjør noe arbeid i bakgrunnen. Ordet "demon" refererer til Maxwells demon. Du kan lære mer i denne
Wikipedia-artikkelen . Som angitt i dokumentasjonen, fortsetter JVM å kjøre programmet (prosessen) til:
- Metoden Runtime.exit () kalles
- Alle Tråder som ikke er demoner fullfører arbeidet sitt (uten feil eller med kastede unntak)
En viktig detalj følger av dette: daemon-tråder kan avsluttes når som helst. Som et resultat er det ingen garantier for integriteten til dataene deres. Følgelig er demon-tråder egnet for visse husholdningsoppgaver. For eksempel har Java en tråd som er ansvarlig for behandling av
finalize()
metodekall, dvs. tråder som er involvert med Garbage Collector (gc). Hver tråd er en del av en gruppe (
Trådgruppe ). Og grupper kan være en del av andre grupper, danne et visst hierarki eller struktur.
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());
}
Grupper bringer orden i trådhåndteringen. I tillegg til grupper har tråder sin egen unntaksbehandler. Ta en titt på et eksempel:
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);
}
Divisjon med null vil forårsake en feil som vil bli fanget opp av handleren. Hvis du ikke spesifiserer din egen behandler, vil JVM påkalle standardbehandleren, som vil sende ut unntakets stabelsporing til StdError. Hver tråd har også en prioritet. Du kan lese mer om prioriteringer i denne artikkelen:
Java Thread Priority in Multithreading .
Oppretter en tråd
Som det fremgår av dokumentasjonen, har vi 2 måter å lage en tråd på. Den første måten er å lage din egen underklasse. For eksempel:
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();
}
}
Som du kan se, skjer arbeidet med oppgaven i metoden
run()
, men selve tråden startes i
start()
metoden. Ikke forveksle disse metodene: hvis vi kaller r-
un()
metoden direkte, vil ingen ny tråd startes. Det er
start()
metoden som ber JVM om å opprette en ny tråd. Dette alternativet der vi arver tråd er allerede dårlig ved at vi inkluderer tråd i klassehierarkiet vårt. Den andre ulempen er at vi begynner å bryte prinsippet om "enkelt ansvar". Det vil si at klassen vår samtidig er ansvarlig for å kontrollere tråden og for en oppgave som skal utføres i denne tråden. Hva er den rette måten? Svaret finnes i samme
run()
metode, som vi overstyrer:
public void run() {
if (target != null) {
target.run();
}
}
Her
target
er noen
java.lang.Runnable
, som vi kan sende når vi oppretter en forekomst av Thread-klassen. Dette betyr at vi kan gjøre dette:
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
har også vært et funksjonelt grensesnitt siden Java 1.8. Dette gjør det mulig å skrive enda vakrere kode for en tråds oppgave:
public static void main(String[] args) {
Runnable task = () -> {
System.out.println("Hello, World!");
};
Thread thread = new Thread(task);
thread.start();
}
Konklusjon
Jeg håper denne diskusjonen klargjør hva en tråd er, hvordan tråder blir til, og hvilke grunnleggende operasjoner som kan utføres med tråder. I
neste del skal vi prøve å forstå hvordan tråder samhandler med hverandre og utforske trådens livssyklus.
Bedre sammen: Java og Thread-klassen. Del II — Synkronisering Bedre sammen: Java og Thread-klassen. Del III — Interaksjon Bedre sammen: Java og Thread-klassen. Del IV — Callable, Future og friends Bedre sammen: Java og Thread-klassen. Del V — Executor, ThreadPool, Fork/Join Better together: Java og Thread-klassen. Del VI – Fyr vekk!
GO TO FULL VERSION