CodeGym/Blog Java/Aleatoriu/Mai bine împreună: Java și clasa Thread. Partea I — Fire ...
John Squirrels
Nivel
San Francisco

Mai bine împreună: Java și clasa Thread. Partea I — Fire de execuție

Publicat în grup

Introducere

Multithreading a fost integrat în Java încă de la început. Deci, să ne uităm pe scurt la acest lucru numit multithreading. Mai bine împreună: Java și clasa Thread.  Partea I - Fire de execuție - 1Luăm ca punct de referință lecția oficială de la Oracle: „ Lecția: Aplicația „Hello World!” ”. Vom modifica ușor codul programului nostru Hello World, după cum urmează:
class HelloWorldApp {
    public static void main(String[] args) {
        System.out.println("Hello, " + args[0]);
    }
}
argseste o matrice de parametri de intrare trecuți atunci când programul este pornit. Salvați acest cod într-un fișier cu un nume care se potrivește cu numele clasei și are extensia .java. Compilați-l folosind utilitarul javac : javac HelloWorldApp.java. Apoi, rulăm codul nostru cu un anumit parametru, de exemplu, „Roger”: java HelloWorldApp Roger Mai bine împreună: Java și clasa Thread.  Partea I — Fire de execuție - 2codul nostru are în prezent un defect grav. Dacă nu treceți niciun argument (adică executați doar „java HelloWorldApp”), atunci obținem o eroare:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
        at HelloWorldApp.main(HelloWorldApp.java:3)
A apărut o excepție (adică o eroare) în firul numit „principal”. Deci, Java are fire? Aici începe călătoria noastră.

Java și fire

Pentru a înțelege ce este un fir, trebuie să înțelegeți cum începe un program Java. Să ne schimbăm codul după cum urmează:
class HelloWorldApp {
    public static void main(String[] args) {
		while (true) {
			// Do nothing
		}
	}
}
Acum să-l compilam din nou cu javac. Pentru comoditate, vom rula codul nostru Java într-o fereastră separată. Pe Windows, acest lucru se poate face astfel: start java HelloWorldApp. Acum vom folosi utilitarul jps pentru a vedea ce informații ne poate spune Java: Mai bine împreună: Java și clasa Thread.  Partea I - Fire de execuție - 3primul număr este PID-ul sau ID-ul procesului. Ce este un proces?
A process is a combination of code and data sharing a common virtual address space.
Cu procese, diferite programe sunt izolate unele de altele pe măsură ce rulează: fiecare aplicație își folosește propria zonă din memorie fără a interfera cu alte programe. Pentru a afla mai multe, vă recomand să citiți acest tutorial: Procese și fire . Un proces nu poate exista fără un fir, deci dacă un proces există, atunci acesta are cel puțin un fir. Dar cum se întâmplă asta în Java? Când pornim un program Java, execuția începe cu mainmetoda. Este ca și cum am păși în program, așa că această mainmetodă specială se numește punctul de intrare. Metoda maintrebuie să fie întotdeauna „public static void”, astfel încât mașina virtuală Java (JVM) să poată începe să execute programul nostru. Pentru mai multe informații, De ce este metoda principală Java statică?. Se pare că lansatorul Java (java.exe sau javaw.exe) este o aplicație C simplă: încarcă diferitele DLL-uri care cuprind de fapt JVM-ul. Lansatorul Java efectuează un set specific de apeluri JNI (Java Native Interface). JNI este un mecanism pentru conectarea lumii mașinii virtuale Java cu lumea C++. Deci, lansatorul nu este JVM-ul în sine, ci mai degrabă un mecanism pentru a-l încărca. Cunoaște comenzile corecte de executat pentru a porni JVM-ul. Știe cum să folosească apelurile JNI pentru a configura mediul necesar. Configurarea acestui mediu include crearea firului principal, care se numește „principal”, desigur. Pentru a ilustra mai bine ce fire există într-un proces Java, folosim jvisualvminstrument, care este inclus cu JDK. Cunoscând pid-ul unui proces, putem vedea imediat informații despre acel proces: jvisualvm --openpid <process id> Mai bine împreună: Java și clasa Thread.  Partea I – Fire de execuție - 4în mod interesant, fiecare fir are propria sa zonă separată în memoria alocată procesului. Această structură de memorie se numește stivă. Un teanc este format din cadre. Un cadru reprezintă activarea unei metode (un apel de metodă neterminat). Un cadru poate fi reprezentat și ca un StackTraceElement (consultați API-ul Java pentru StackTraceElement ). Puteți găsi mai multe informații despre memoria alocată fiecărui thread în discuție aici: " Cum alocă Java (JVM) stiva pentru fiecare thread ". Dacă vă uitați la API-ul Java și căutați cuvântul „Thread”, veți găsi java.lang.Threadclasă. Aceasta este clasa care reprezintă un fir în Java și va trebui să lucrăm cu el. Mai bine împreună: Java și clasa Thread.  Partea I - Fire de execuție - 5

java.lang.Thread

În Java, un fir este reprezentat de o instanță a java.lang.Threadclasei. Ar trebui să înțelegeți imediat că instanțele clasei Thread nu sunt ele însele fire de execuție. Acesta este doar un fel de API pentru firele de execuție de nivel scăzut gestionate de JVM și sistemul de operare. Când pornim JVM-ul folosind lansatorul Java, acesta creează un mainfir numit „principal” și alte câteva fire de execuție. După cum se menționează în JavaDoc pentru clasa Thread: When a Java Virtual Machine starts up, there is usually a single non-daemon thread. Există 2 tipuri de fire: demoni și non-daemoni. Firele Daemon sunt fire de fundal (de întreținere) care lucrează în fundal. Cuvântul „daemon” se referă la demonul lui Maxwell. Puteți afla mai multe în acest articol Wikipedia . După cum se precizează în documentație, JVM continuă să execute programul (procesul) până când:
  • Este apelată metoda Runtime.exit ().
  • Toate firele NON-daemon își termină munca (fără erori sau cu excepții aruncate)
De aici rezultă un detaliu important: firele de demon pot fi terminate în orice moment. Ca urmare, nu există garanții cu privire la integritatea datelor lor. În consecință, firele de demon sunt potrivite pentru anumite sarcini de menaj. De exemplu, Java are un fir care este responsabil pentru procesarea finalize()apelurilor de metodă, adică fire implicate cu Garbage Collector (gc). Fiecare thread face parte dintr-un grup ( ThreadGroup ). Și grupurile pot face parte din alte grupuri, formând o anumită ierarhie sau structură.
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());
}
Grupurile aduc ordine în gestionarea firelor. Pe lângă grupuri, firele au propriul lor handler de excepții. Aruncă o privire la un exemplu:
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);
}
Împărțirea cu zero va provoca o eroare care va fi surprinsă de handler. Dacă nu vă specificați propriul handler, atunci JVM-ul va invoca handler-ul implicit, care va scoate urmărirea stivei excepției la StdError. Fiecare thread are, de asemenea, o prioritate. Puteți citi mai multe despre priorități în acest articol: Java Thread Priority in Multithreading .

Crearea unui fir

După cum se menționează în documentație, avem 2 moduri de a crea un fir. Prima modalitate este să vă creați propria subclasă. De exemplu:
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();
    }
}
După cum puteți vedea, munca sarcinii se întâmplă în metodă run(), dar firul în sine este început în start()metodă. Nu confundați aceste metode: dacă apelăm un()direct metoda r, atunci nu va fi pornit un fir nou. Este start()metoda care solicită JVM-ului să creeze un fir nou. Această opțiune în care moștenim Thread este deja proastă, deoarece includem Thread în ierarhia claselor noastre. Al doilea dezavantaj este că începem să încălcăm principiul „responsabilității unice”. Adică, clasa noastră este responsabilă simultan de controlul firului de execuție și de îndeplinirea unei sarcini în acest thread. Care este calea corectă? Răspunsul se găsește în aceeași run()metodă, pe care o înlocuim:
public void run() {
	if (target != null) {
		target.run();
	}
}
Iată targetcâteva java.lang.Runnable, pe care le putem transmite atunci când creăm o instanță a clasei Thread. Aceasta înseamnă că putem face asta:
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();
    }
}
Runnablea fost, de asemenea, o interfață funcțională începând cu Java 1.8. Acest lucru face posibilă scrierea unui cod și mai frumos pentru sarcina unui fir:
public static void main(String[] args) {
	Runnable task = () -> {
		System.out.println("Hello, World!");
	};
	Thread thread = new Thread(task);
	thread.start();
}

Concluzie

Sper că această discuție clarifică ce este un fir de execuție, cum apar firele de execuție și ce operațiuni de bază pot fi efectuate cu firele de execuție. În următoarea parte , vom încerca să înțelegem cum interacționează firele între ele și vom explora ciclul de viață al firului. Mai bine împreună: Java și clasa Thread. Partea a II-a — Sincronizare Mai bine împreună: Java și clasa Thread. Partea a III-a — Interacțiunea Mai bine împreună: Java și clasa Thread. Partea a IV-a — Apelabil, viitor și prieteni Mai bine împreună: Java și clasa Thread. Partea V — Executor, ThreadPool, Fork/Join Better împreună: Java și clasa Thread. Partea a VI-a — Foc departe!
Comentarii
  • Popular
  • Nou
  • Vechi
Trebuie să fii conectat pentru a lăsa un comentariu
Această pagină nu are încă niciun comentariu