Multithread in Java

La Java Virtual Machine supporta il calcolo parallelo . Tutti i calcoli possono essere eseguiti nel contesto di uno o più thread. Possiamo facilmente configurare l'accesso alla stessa risorsa o oggetto per più thread, nonché configurare un thread per eseguire un singolo blocco di codice.

Qualsiasi sviluppatore deve sincronizzare il lavoro con i thread durante le operazioni di lettura e scrittura per le risorse a cui sono allocati più thread.

È importante che al momento dell'accesso alla risorsa si disponga di dati aggiornati in modo che un altro thread possa modificarli e ottenere le informazioni più aggiornate. Anche se prendiamo l'esempio di un conto corrente bancario, fino a quando non ci sono arrivati ​​i soldi, non puoi usarlo, quindi è importante avere sempre dati aggiornati. Java ha classi speciali per la sincronizzazione e la gestione dei thread.

Infila gli oggetti

Tutto inizia con il thread principale (principale), ovvero almeno il tuo programma ha già un thread in esecuzione. Il thread principale può creare altri thread utilizzando Callable o Runnable . La creazione differisce solo nel risultato restituito, Runnable non restituisce un risultato e non può generare un'eccezione verificata. Pertanto, hai una buona opportunità per creare un lavoro efficiente con i file, ma questo è molto pericoloso e devi stare attento.

È anche possibile pianificare l'esecuzione del thread su un core della CPU separato. Il sistema può facilmente spostarsi tra i thread ed eseguire un thread specifico con le giuste impostazioni: ovvero, il thread che legge i dati viene eseguito per primo, non appena abbiamo i dati, quindi li passiamo al thread che è responsabile della convalida, dopodiché lo passiamo al thread per eseguire alcune logiche di business e un nuovo thread le riscrive. In una situazione del genere, 4 thread elaborano i dati a turno e tutto funzionerà più velocemente di un thread. Ognuno di questi flussi viene convertito in un flusso del sistema operativo nativo, ma il modo in cui verrà convertito dipende dall'implementazione JVM.

La classe Thread viene utilizzata per creare e lavorare con i thread. Ha meccanismi di controllo standard, oltre a quelli astratti, come classi e raccolte da java.util.concurrent .

Sincronizzazione dei thread in Java

La comunicazione viene fornita condividendo l'accesso agli oggetti. Questo è molto efficace, ma allo stesso tempo è molto facile commettere un errore durante il lavoro. Gli errori si verificano in due casi: interferenza del thread - quando un altro thread interferisce con il tuo thread ed errori di coerenza della memoria - coerenza della memoria. Per risolvere e prevenire questi errori, abbiamo diversi metodi di sincronizzazione.

La sincronizzazione dei thread in Java è gestita dai monitor, questo è un meccanismo di alto livello che consente a un solo thread di eseguire un blocco di codice protetto dallo stesso monitor alla volta. Il comportamento dei monitor è considerato in termini di blocchi; un monitor - un lucchetto.

La sincronizzazione ha diversi punti importanti a cui devi prestare attenzione. Il primo punto è l'esclusione reciproca: solo un thread può possedere il monitor, quindi la sincronizzazione sul monitor implica che una volta che un thread entra in un blocco sincronizzato protetto dal monitor, nessun altro thread può entrare nel blocco protetto dal monitor. il primo thread esce dal blocco sincronizzato. Cioè, più thread non possono accedere contemporaneamente allo stesso blocco sincronizzato.

Ma la sincronizzazione non è solo mutua esclusione. La sincronizzazione garantisce che i dati scritti in memoria prima o all'interno di un blocco sincronizzato diventino visibili ad altri thread sincronizzati sullo stesso monitor. Dopo essere usciti dal blocco, rilasciamo il monitor e un altro thread può prenderlo e avviare l'esecuzione di questo blocco di codice.

Quando un nuovo thread acquisisce il monitor, otteniamo l'accesso e la possibilità di eseguire quel blocco di codice, e in quel momento le variabili verranno caricate dalla memoria principale. Quindi possiamo vedere tutte le voci rese visibili dalla versione precedente del monitor.

Una lettura-scrittura su un campo è un'operazione atomica se il campo è dichiarato volatile o protetto da un blocco univoco acquisito prima di qualsiasi lettura-scrittura. Ma se riscontri ancora un errore, ricevi un errore relativo al riordino (modifica dell'ordine, riordino). Si manifesta in programmi multi-thread sincronizzati in modo errato, in cui un thread può osservare gli effetti prodotti da altri thread.

L'effetto di mutua esclusione e sincronizzazione dei thread, ovvero il loro corretto funzionamento, si ottiene solo inserendo un blocco o un metodo sincronizzato che acquisisce implicitamente un blocco o ottenendo esplicitamente un blocco. Ne parleremo di seguito. Entrambi i modi di lavorare influenzano la tua memoria ed è importante non dimenticare di lavorare con variabili volatili .

Campi volatili in Java

Se una variabile è contrassegnata come volatile , è disponibile a livello globale. Ciò significa che se un thread accede a una variabile volatile , otterrà il suo valore prima di utilizzare il valore dalla cache.

Una scrittura funziona come una versione monitor e una lettura funziona come un'acquisizione monitor. L'accesso avviene in una relazione del tipo “eseguita prima”. Se lo capisci, tutto ciò che sarà visibile al thread A quando accede a una variabile volatile è la variabile per il thread B. Cioè, hai la garanzia di non perdere le modifiche da altri thread.

Le variabili volatili sono atomiche, ovvero, durante la lettura di una tale variabile, viene utilizzato lo stesso effetto di quando si ottiene un blocco: i dati in memoria vengono dichiarati non validi o errati e il valore della variabile volatile viene nuovamente letto dalla memoria . Durante la scrittura, viene utilizzato l'effetto sulla memoria, così come quando si rilascia un blocco: un campo volatile viene scritto nella memoria.

Java simultaneo

Se vuoi creare un'applicazione super efficiente e multi-thread, devi usare le classi della libreria JavaConcurrent , che si trovano nel pacchetto java.util.concurrent .

La libreria è molto voluminosa e ha diverse funzionalità, quindi diamo un'occhiata a cosa c'è dentro e dividiamola in alcuni moduli:

Java simultaneo

Concurrent Collections è un insieme di raccolte per lavorare in un ambiente multi-thread. Invece del wrapper di base Collections.synchronizedList con il blocco dell'accesso all'intera raccolta, vengono utilizzati blocchi sui segmenti di dati o algoritmi senza attesa per leggere i dati in parallelo.

Code: code non bloccanti e bloccanti per lavorare in un ambiente multi-thread. Le code non bloccanti si concentrano sulla velocità e sul funzionamento senza bloccare i thread. Le code di blocco sono adatte per lavorare quando è necessario "rallentare" i thread Producer o Consumer . Ad esempio, in una situazione in cui alcune delle condizioni non sono soddisfatte, la coda è vuota o piena o non esiste un consumatore libero 'a.

I sincronizzatori sono utility per la sincronizzazione dei thread. Sono un'arma potente nel calcolo "parallelo".

Executors è un framework per la creazione più comoda e semplice di pool di thread, è facile impostare la pianificazione di attività asincrone con l'ottenimento di risultati.

I blocchi sono molti meccanismi flessibili di sincronizzazione dei thread rispetto ai meccanismi di base sincronizzati , attendere , notificare , notificare .

Gli atomi sono classi che possono supportare operazioni atomiche su primitive e riferimenti.