Introduzione ai thread Java

Questo articolo, uno dei primi mai pubblicati da JavaWorld, descrive come i thread vengono implementati nel linguaggio di programmazione Java, a partire da una panoramica generale dei thread.

In poche parole, un thread è il percorso di esecuzione di un programma. La maggior parte dei programmi scritti oggi vengono eseguiti come un singolo thread, causando problemi quando devono verificarsi più eventi o azioni contemporaneamente. Diciamo, ad esempio, che un programma non è in grado di disegnare immagini durante la lettura delle sequenze di tasti. Il programma deve dare la sua piena attenzione agli input da tastiera che non hanno la capacità di gestire più di un evento alla volta. La soluzione ideale a questo problema è l'esecuzione senza interruzioni di due o più sezioni di un programma contemporaneamente. Threads ci permette di farlo.

Imparare a conoscere i thread Java

Questo articolo fa parte dell'archivio dei contenuti tecnici di JavaWorld. Vedere quanto segue per ulteriori informazioni sui thread Java e sulla concorrenza:

Comprensione dei thread Java ( serie Java 101 , 2002):

  • Parte 1: presentazione di thread ed eseguibili
  • Parte 2: sincronizzazione dei thread
  • Parte 3: pianificazione dei thread e attesa / notifica
  • Parte 4: gruppi di thread e volatilità

Articoli Correlati

  • Java hyper-threaded: utilizzo dell'API di concorrenza Java (2006)
  • Monitor migliori per programmi multithread (2007)
  • Comprensione della concorrenza tra attori, parte 1 (2009)
  • Rilevamento e manipolazione del filo sospeso (2011)

Controlla anche la mappa del sito JavaWorld e il motore di ricerca .

Le applicazioni multithread forniscono la loro potenza potente eseguendo molti thread contemporaneamente all'interno di un singolo programma. Da un punto di vista logico, il multithreading significa che più righe di un singolo programma possono essere eseguite contemporaneamente, tuttavia, non è la stessa cosa che avviare un programma due volte e dire che ci sono più righe di un programma in esecuzione allo stesso tempo. In questo caso, il sistema operativo tratta i programmi come due processi separati e distinti. In Unix, il fork di un processo crea un processo figlio con uno spazio di indirizzi diverso sia per il codice che per i dati. Però,fork()crea un sacco di sovraccarico per il sistema operativo, rendendolo un'operazione che richiede molta CPU. Avviando invece un thread, viene creato un percorso di esecuzione efficiente pur condividendo l'area dati originale dal genitore. L'idea di condividere l'area dati è molto vantaggiosa, ma solleva alcune aree di preoccupazione di cui parleremo più avanti.

Creazione di thread

I creatori di Java hanno graziosamente progettato due modi per creare thread: implementare un'interfaccia ed estendere una classe. L'estensione di una classe è il modo in cui Java eredita metodi e variabili da una classe genitore. In questo caso, è possibile estendere o ereditare solo da una singola classe genitore. Questa limitazione all'interno di Java può essere superata implementando le interfacce, che è il modo più comune per creare thread. (Si noti che l'atto di ereditare consente semplicemente di eseguire la classe come thread. Spetta alla classe eseguire l' start()esecuzione, ecc.)

Le interfacce forniscono ai programmatori un modo per gettare le basi di una classe. Sono usati per progettare i requisiti per un insieme di classi da implementare. L'interfaccia imposta tutto e la classe o le classi che implementano l'interfaccia fanno tutto il lavoro. Il diverso insieme di classi che implementano l'interfaccia devono seguire le stesse regole.

Ci sono alcune differenze tra una classe e un'interfaccia. Innanzitutto, un'interfaccia può contenere solo metodi astratti e / o variabili finali statiche (costanti). Le classi, d'altra parte, possono implementare metodi e contenere variabili che non sono costanti. In secondo luogo, un'interfaccia non può implementare alcun metodo. Una classe che implementa un'interfaccia deve implementare tutti i metodi definiti in tale interfaccia. Un'interfaccia ha la capacità di estendersi da altre interfacce e (a differenza delle classi) può estendersi da più interfacce. Inoltre, non è possibile istanziare un'interfaccia con il nuovo operatore; ad esempio, Runnable a=new Runnable();non è consentito.

Il primo metodo per creare un thread è semplicemente estenderlo dalla Threadclasse. Fallo solo se la classe che devi eseguire come thread non deve mai essere estesa da un'altra classe. La Threadclasse è definita nel pacchetto java.lang, che deve essere importato in modo che le nostre classi siano consapevoli della sua definizione.

import java.lang.*; public class Counter extends Thread { public void run() { .... } }

L'esempio precedente crea una nuova classe Counterche estende la Threadclasse e sovrascrive il Thread.run()metodo per la propria implementazione. Il run()metodo è dove Counterviene svolto tutto il lavoro del thread della classe. La stessa classe può essere creata implementando Runnable:

import java.lang.*; public class Counter implements Runnable { Thread T; public void run() { .... } }

Qui, il run()metodo astratto è definito nell'interfaccia Runnable e viene implementato. Nota che abbiamo un'istanza della Threadclasse come variabile della Counterclasse. L'unica differenza tra i due metodi è che implementando Runnable, c'è una maggiore flessibilità nella creazione della classe Counter. Nell'esempio sopra, esiste ancora l'opportunità di estendere la Counterclasse, se necessario. La maggior parte delle classi create che devono essere eseguite come thread implementeranno Runnable poiché probabilmente stanno estendendo alcune altre funzionalità da un'altra classe.

Non pensare che l'interfaccia Runnable stia facendo un lavoro reale quando il thread viene eseguito. È semplicemente una classe creata per dare un'idea sul design della Threadclasse. In effetti, è molto piccolo e contiene un solo metodo astratto. Ecco la definizione dell'interfaccia Runnable direttamente dalla sorgente Java:

package java.lang; public interface Runnable { public abstract void run(); }

Questo è tutto ciò che c'è nell'interfaccia Runnable. Un'interfaccia fornisce solo un progetto su cui implementare le classi. Nel caso dell'interfaccia Runnable, forza la definizione del solo run()metodo. Pertanto, la maggior parte del lavoro viene svolto in Threadclasse. Uno sguardo più da vicino a una sezione nella definizione della Threadclasse darà un'idea di cosa sta realmente accadendo:

public class Thread implements Runnable { ... public void run() { if (target != null) { target.run(); } } ... }

Dallo snippet di codice precedente è evidente che la classe Thread implementa anche l'interfaccia Runnable. Thread. run()controlla per assicurarsi che la classe di destinazione (la classe che verrà eseguita come thread) non sia uguale a null, quindi esegue il run()metodo della destinazione. Quando ciò accade, il run()metodo della destinazione verrà eseguito come un proprio thread.

Avvio e arresto

Poiché i diversi modi per creare un'istanza di un thread sono ora evidenti, discuteremo dell'implementazione dei thread iniziando con i modi disponibili per avviarli e arrestarli utilizzando una piccola applet contenente un thread per illustrare i meccanismi:

Esempio di CounterThread e codice sorgente

L'applet sopra inizierà a contare da 0 visualizzando il suo output sia sullo schermo che sulla console. Una rapida occhiata potrebbe dare l'impressione che il programma inizi a contare e visualizzare tutti i numeri, ma non è così. Un esame più attento dell'esecuzione di questa applet rivelerà la sua vera identità.

In this case, the CounterThread class was forced to implement Runnable since it extended the class Applet. As in all applets, the init() method gets executed first. In init(), the variable Count is initialized to zero and a new instance of the Thread class is created. By passing this to the Thread constructor, the new thread will know which object to run. In this case this is a reference to CounterThread. After the thread is created it needs to be started. The call to start() will call the target's run() method, which is CounterThread.run(). The call to start() will return right away and the thread will start executing at the same time. Note that the run() method is an infinite loop. It is infinite because once the run() method exits, the thread stops executing. The run() method will increment the variable Count, sleep for 10 milliseconds and send a request to refresh the applet's display.

Note that it is important to sleep somewhere in a thread. If not, the thread will consume all CPU time for the process and will not allow any other methods such as threads to be executed. Another way to cease the execution of a thread is to call the stop() method. In this example, the thread stops when the mouse is pressed while the cursor is in the applet. Depending on the speed of the computer the applet runs on, not every number will be displayed, because the incrementing is done independent of the painting of the applet. The applet can not be refreshed at every request, so the OS will queue the requests and successive refresh requests will be satisfied with one refresh. While the refreshes are queuing up, the Count is still being incremented but not displayed.

Suspending and resuming

Once a thread is stopped, it cannot be restarted with the start() command, since stop() will terminate the execution of a thread. Instead you can pause the execution of a thread with the sleep() method. The thread will sleep for a certain period of time and then begin executing when the time limit is reached. But, this is not ideal if the thread needs to be started when a certain event occurs. In this case, the suspend() method allows a thread to temporarily cease executing and the resume() method allows the suspended thread to start again. The following applet shows the above example modified to suspend and resume the applet.

public class CounterThread2 extends Applet implements Runnable { Thread t; int Count; boolean suspended; public boolean mouseDown(Event e,int x, int y) { if(suspended) t.resume(); else t.suspend(); suspended = !suspended; return true; } ... }

CounterThread2 Example and Source code

Per tenere traccia dello stato corrente dell'applet, suspendedviene utilizzata la variabile booleana . Distinguere i diversi stati di un'applet è importante perché alcuni metodi generano eccezioni se vengono chiamati nello stato sbagliato. Ad esempio, se l'applet è stata avviata e arrestata, l'esecuzione del start()metodo genererà IllegalThreadStateExceptionun'eccezione.