CodeGym /Java Blog /Random-IT /Multithread in Java
John Squirrels
Livello 41
San Francisco

Multithread in Java

Pubblicato nel gruppo Random-IT
CIAO! Prima di tutto congratulazioni: sei arrivato all'argomento del Multithreading in Java! Questo è un risultato serio: hai fatto molta strada. Ma preparati: questo è uno degli argomenti più difficili del corso. E non è che stiamo usando classi complesse o molti metodi qui: infatti, ne useremo meno di venti. È più che avrai bisogno di cambiare leggermente il modo in cui pensi. In precedenza, i programmi venivano eseguiti in sequenza. Alcune righe di codice venivano dopo altre, alcuni metodi venivano dopo altri e tutto era sostanzialmente chiaro. Per prima cosa, abbiamo calcolato qualcosa, quindi abbiamo visualizzato il risultato sulla console e quindi il programma è terminato. Per comprendere il multithreading, è meglio pensare in termini di parallelismo. Partiamo da qualcosa di abbastanza semplice: ) Immagina che la tua famiglia si trasferisca da una casa all'altra. Raccogliere tutti i tuoi libri sarà una parte importante del trasloco. Hai accumulato molti libri e devi metterli nelle scatole. Al momento, sei l'unico disponibile. La mamma sta preparando il cibo, il fratello sta preparando i vestiti e la sorella è andata al negozio. Da solo, puoi cavartela in qualche modo. Prima o poi, completerai l'attività da solo, ma ci vorrà molto tempo. Tuttavia, tua sorella tornerà dal negozio tra 20 minuti e non ha nient'altro da fare. Quindi può unirsi a te. Il compito non è cambiato: mettere i libri nelle scatole. Ma viene eseguito due volte più velocemente. Perché? Perché il lavoro sta accadendo in parallelo. Due diversi "thread" (tu e tua sorella) stanno eseguendo la stessa attività contemporaneamente. E se non cambia nulla, allora ci sarà un'enorme differenza di tempo rispetto alla situazione in cui fai tutto da solo. Se il fratello finisce presto il suo lavoro, può aiutarti e le cose andranno ancora più velocemente.

Problemi risolti dal multithreading

Il multithreading è stato in realtà inventato per raggiungere due obiettivi importanti:
  1. Fai più cose contemporaneamente.

    Nell'esempio sopra, diversi thread (membri della famiglia) hanno eseguito diverse azioni in parallelo: hanno lavato i piatti, sono andati al negozio e hanno impacchettato le cose.

    Possiamo offrire un esempio più strettamente legato alla programmazione. Supponiamo di avere un programma con un'interfaccia utente. Quando fai clic su "Continua" nel programma, dovrebbero essere eseguiti alcuni calcoli e l'utente dovrebbe vedere la seguente schermata. Se queste azioni fossero eseguite in sequenza, il programma si bloccherebbe solo dopo che l'utente fa clic sul pulsante "Continua". L'utente vedrà la schermata con la schermata del pulsante 'Continua' finché il programma non esegue tutti i calcoli interni e raggiunge la parte in cui l'interfaccia utente viene aggiornata.

    Bene, immagino che aspetteremo un paio di minuti!

    Il multithreading in Java: cos'è, vantaggi e insidie ​​comuni - 3

    Oppure potremmo rielaborare il nostro programma o, come dicono i programmatori, "parallelizzarlo". Eseguiamo i nostri calcoli su un thread e disegniamo l'interfaccia utente su un altro. La maggior parte dei computer dispone di risorse sufficienti per eseguire questa operazione. Se seguiamo questa strada, il programma non si bloccherà e l'utente si sposterà agevolmente tra le schermate senza preoccuparsi di ciò che sta accadendo all'interno. Uno non interferisce con l'altro :)

  2. Eseguire i calcoli più rapidamente.

    Tutto è molto più semplice qui. Se il nostro processore ha più core e la maggior parte dei processori oggi lo fa, allora diversi core possono gestire il nostro elenco di attività in parallelo. Ovviamente, se dobbiamo eseguire 1000 task e ciascuno impiega un secondo, un core può terminare l'elenco in 1000 secondi, due core in 500 secondi, tre in poco più di 333 secondi, ecc.

Ma come hai già letto in questa lezione, i sistemi odierni sono molto intelligenti e anche su un solo core di elaborazione sono in grado di raggiungere il parallelismo, o meglio lo pseudo-parallelismo, in cui le attività vengono eseguite alternativamente. Passiamo dalle generalità alle specifiche e impariamo a conoscere la classe più importante nella libreria multithreading Java: java.lang.Thread. In senso stretto, i thread Java sono rappresentati da istanze della classe Thread . Ciò significa che per creare ed eseguire 10 thread, sono necessarie 10 istanze di questa classe. Scriviamo l'esempio più semplice:

public class MyFirstThread extends Thread {

   @Override
   public void run() {
       System.out.println("I'm Thread! My name is " + getName());
   }
}
Per creare ed eseguire thread, dobbiamo creare una classe, farla ereditare java.lang . Thread class e sovrascrivere il suo metodo run() . Quest'ultimo requisito è molto importante. È nel metodo run() che definiamo la logica per l'esecuzione del nostro thread. Ora, se creiamo ed eseguiamo un'istanza di MyFirstThread , il metodo run() visualizzerà una riga con un nome: il metodo getName() visualizza il nome di 'sistema' del thread, che viene assegnato automaticamente. Ma perché stiamo parlando provvisoriamente? Creiamone uno e scopriamolo!

public class Main {

   public static void main(String[] args) {

       for (int i = 0; i < 10; i++) {

           MyFirstThread thread = new MyFirstThread();
           thread.start();
       }
   }
}
Output della console: sono Thread! Mi chiamo Thread-2, sono Thread! Mi chiamo Thread-1, sono Thread! Mi chiamo Thread-0 Sono Thread! Mi chiamo Thread-3, sono Thread! Mi chiamo Thread-6, sono Thread! Mi chiamo Thread-7, sono Thread! Mi chiamo Thread-4, sono Thread! Mi chiamo Thread-5, sono Thread! Mi chiamo Thread-9, sono Thread! Mi chiamo Thread-8 Creiamo 10 thread ( oggetti MyFirstThread , che ereditano Thread ) e avviamoli chiamando il metodo start() su ogni oggetto. Dopo aver chiamato il metodo start() , viene eseguita la logica nel metodo run() . Nota: i nomi dei thread non sono in ordine. È strano che non fossero in sequenza:, Thread-1 , Thread-2 e così via? Come accade, questo è un esempio di un momento in cui il pensiero "sequenziale" non si adatta. Il problema è che abbiamo fornito solo comandi per creare ed eseguire 10 thread. Il thread scheduler, uno speciale meccanismo del sistema operativo, decide il loro ordine di esecuzione. Il suo design preciso e la strategia decisionale sono argomenti per una discussione approfondita che non ci addentreremo in questo momento. La cosa principale da ricordare è che il programmatore non può controllare l'ordine di esecuzione dei thread. Per comprendere la gravità della situazione, prova a eseguire il metodo main() nell'esempio sopra un altro paio di volte. Output della console alla seconda esecuzione: io sono Filo! Mi chiamo Thread-0 Sono Thread! Mi chiamo Thread-4, sono Thread! Mi chiamo Thread-3, sono Thread! Mi chiamo Thread-2, sono Thread! Mi chiamo Thread-1, sono Thread! Mi chiamo Thread-5, sono Thread! Mi chiamo Thread-6, sono Thread! Mi chiamo Thread-8, sono Thread! Mi chiamo Thread-9, sono Thread! Mi chiamo Thread-7 Uscita della console dalla terza esecuzione: sono Thread! Mi chiamo Thread-0 Sono Thread! Mi chiamo Thread-3, sono Thread! Mi chiamo Thread-1, sono Thread! Mi chiamo Thread-2, sono Thread! Mi chiamo Thread-6, sono Thread! Mi chiamo Thread-4, sono Thread! Mi chiamo Thread-9, sono Thread! Mi chiamo Thread-5, sono Thread! Mi chiamo Thread-7, sono Thread! Mi chiamo Thread-8

Problemi creati dal multithreading

Nel nostro esempio con i libri, hai visto che il multithreading risolve compiti molto importanti e può rendere i nostri programmi più veloci. Spesso molte volte più veloce. Ma il multithreading è considerato un argomento difficile. Infatti, se usato impropriamente, crea problemi invece di risolverli. Quando dico "crea problemi", non intendo in senso astratto. Ci sono due problemi specifici che il multithreading può creare: deadlock e race condition. Il deadlock è una situazione in cui più thread sono in attesa di risorse detenute l'uno dall'altro e nessuno di essi può continuare a essere eseguito. Ne parleremo meglio nelle lezioni successive. Il seguente esempio sarà sufficiente per ora: Il multithreading in Java: cos'è, vantaggi e insidie ​​comuni - 4immagina che Thread-1 interagisca con qualche Object-1 e che Thread-2 interagisca con Object-2. Inoltre, il programma è scritto in modo che:
  1. Thread-1 smette di interagire con Object-1 e passa a Object-2 non appena Thread-2 smette di interagire con Object-2 e passa a Object-1.
  2. Thread-2 smette di interagire con Object-2 e passa a Object-1 non appena Thread-1 smette di interagire con Object-1 e passa a Object-2.
Anche senza una profonda conoscenza del multithreading, puoi facilmente vedere che non accadrà nulla. I fili non si scambieranno mai di posto e si aspetteranno l'un l'altro per sempre. L'errore sembra ovvio, ma in realtà non lo è. Puoi farlo facilmente in un programma. Prenderemo in considerazione esempi di codice che causano deadlock nelle lezioni successive. A proposito, Quora ha un ottimo esempio di vita reale che spiega quale stalloÈ. 'In alcuni stati dell'India, non ti vendono terreni agricoli a meno che tu non sia un agricoltore registrato. Tuttavia, non ti registreranno come agricoltore se non possiedi terreni agricoli». Grande! Cosa possiamo dire?! :) Ora parliamo delle condizioni di gara. Una race condition è un errore di progettazione in un sistema o un'applicazione multithread, in cui il funzionamento del sistema o dell'applicazione dipende dall'ordine in cui vengono eseguite parti del codice. Ricorda, il nostro esempio in cui abbiamo iniziato i thread:

public class MyFirstThread extends Thread {

   @Override
   public void run() {
       System.out.println("Thread executed: " + getName());
   }
}

public class Main {

   public static void main(String[] args) {

       for (int i = 0; i < 10; i++) {

           MyFirstThread thread = new MyFirstThread();
           thread.start();
       }
   }
}
Ora immagina che il programma sia responsabile dell'esecuzione di un robot che cuoce il cibo! Thread-0 tira fuori le uova dal frigo. Thread-1 accende i fornelli. Thread-2 prende una padella e la mette sul fornello. Thread-3 accende la stufa. Thread-4 versa l'olio nella padella. Thread-5 rompe le uova e le versa nella padella. Thread-6 getta i gusci d'uovo nel cestino. Thread-7 rimuove le uova cotte dal bruciatore. Thread-8 mette le uova cotte su un piatto. Thread-9 lava i piatti. Guarda i risultati del nostro programma: Thread eseguito: Thread-0 Thread eseguito: Thread-2 Thread eseguito Thread-1 Thread eseguito: Thread-4 Thread eseguito: Thread-9 Thread eseguito: Thread-5 Thread eseguito: Thread-8 Thread eseguito: Thread-7 Thread eseguito: Thread-3 È una commedia di routine? :) E tutto perché il lavoro del nostro programma dipende dall'ordine di esecuzione dei thread. Alla minima violazione della sequenza richiesta, la nostra cucina si trasforma in un inferno e un folle robot distrugge tutto ciò che la circonda. Questo è anche un problema comune nella programmazione multithread. Ne sentirete parlare più di una volta. Per concludere questa lezione, vorrei consigliare un libro sul multithreading. Il multithreading in Java: cos'è, vantaggi e insidie ​​comuni - 6'Java Concurrency in Practice' è stato scritto nel 2006, ma non ha perso la sua rilevanza. È dedicato alla programmazione Java multithread, dalle basi agli errori e agli antipattern più comuni. Se un giorno deciderai di diventare un guru del multithreading, questo libro è assolutamente da leggere. Ci vediamo alle prossime lezioni! :)
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION