Problemi risolti dal multithreading
Il multithreading è stato in realtà inventato per raggiungere due obiettivi importanti:-
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!
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 :)
-
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.
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:
- 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.
- 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.
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. 
GO TO FULL VERSION