JavaRush /Java Blog /Random-IT /Sincronizzazione dei thread. Operatore sincronizzato in J...

Sincronizzazione dei thread. Operatore sincronizzato in Java

Pubblicato nel gruppo Random-IT
Ciao! Oggi continueremo a considerare le caratteristiche della programmazione multi-thread e parleremo della sincronizzazione dei thread.
Sincronizzazione dei thread.  Operatore sincronizzato - 1
Cos'è la "sincronizzazione"? Al di fuori dell'ambito della programmazione, questo si riferisce a qualche tipo di configurazione che consente a due dispositivi o programmi di lavorare insieme. Ad esempio, uno smartphone e un computer possono essere sincronizzati con un account Google e un account personale su un sito Web può essere sincronizzato con account sui social network per accedere utilizzandoli. La sincronizzazione dei thread ha un significato simile: imposta il modo in cui i thread interagiscono tra loro. Nelle lezioni precedenti, i nostri thread vivevano e funzionavano separatamente gli uni dagli altri. Uno contava qualcosa, il secondo dormiva, il terzo visualizzava qualcosa sulla console, ma non interagivano tra loro. Nei programmi reali tali situazioni sono rare. Diversi thread possono lavorare attivamente, ad esempio, con lo stesso set di dati e modificarne qualcosa. Questo crea problemi. Immagina che più thread scrivano testo nella stessa posizione, ad esempio un file di testo o la console. Questo file o console in questo caso diventa una risorsa condivisa. I thread non conoscono l'esistenza degli altri, quindi scrivono semplicemente tutto ciò che possono gestire nel tempo assegnato loro dallo scheduler dei thread. In una recente lezione del corso abbiamo avuto un esempio di cosa questo porterebbe, ricordiamolo: Sincronizzazione dei thread.  Operatore sincronizzato - 2il motivo sta nel fatto che i thread lavoravano con una risorsa condivisa, la console, senza coordinare le azioni tra loro. Se lo scheduler dei thread ha assegnato tempo al Thread-1, scrive immediatamente tutto sulla console. Ciò che gli altri thread sono già riusciti a scrivere o non sono riusciti a scrivere non è importante. Il risultato, come potete vedere, è disastroso. Pertanto, nella programmazione multi-thread, è stato introdotto un concetto speciale mutex (dall'inglese "mutex", "mutua esclusione" - "mutua esclusione") . Lo scopo di un mutex è fornire un meccanismo affinché solo un thread alla volta abbia accesso a un oggetto. Se il thread-1 ha acquisito il mutex dell'oggetto A, gli altri thread non avranno accesso ad esso per modificare nulla al suo interno. Fino al rilascio del mutex dell'oggetto A, i thread rimanenti saranno costretti ad attendere. Esempio di vita reale: immagina che tu e altri 10 sconosciuti stiate partecipando a un corso di formazione. È necessario, a turno, esprimere idee e discutere qualcosa. Ma, poiché vi vedete per la prima volta, per non interrompervi costantemente e non cadere nel caos, usate la regola della "palla parlante": può parlare solo una persona, quella che ha la palla in mano le sue mani. In questo modo la discussione risulta adeguata e fruttuosa. Quindi, il mutex, in sostanza, è una palla del genere. Se il mutex di un oggetto è nelle mani di un thread, gli altri thread non saranno in grado di accedere all'oggetto. Non devi fare nulla per creare un mutex: è già integrato nella classe Object, il che significa che ogni oggetto in Java ce l'ha.

Come funziona l'operatore sincronizzato in Java

Facciamo conoscenza con una nuova parola chiave: sincronizzato . Contrassegna un certo pezzo del nostro codice. Se un blocco di codice è contrassegnato con la parola chiave sincronizzata, significa che il blocco può essere eseguito solo da un thread alla volta. La sincronizzazione può essere implementata in diversi modi. Ad esempio, crea un intero metodo sincronizzato:
public synchronized void doSomething() {

   //...method logic
}
Oppure scrivi un blocco di codice in cui viene eseguita la sincronizzazione su qualche oggetto:
public class Main {

   private Object obj = new Object();

   public void doSomething() {

       //...some logic available to all threads

       synchronized (obj) {

           //logic that is only available to one thread at a time
       }
   }
}
Il significato è semplice. Se un thread entra in un blocco di codice contrassegnato dalla parola sincronizzato, acquisisce immediatamente il mutex dell'oggetto e tutti gli altri thread che tentano di entrare nello stesso blocco o metodo sono costretti ad attendere finché il thread precedente non completa il suo lavoro e rilascia il file tenere sotto controllo. Sincronizzazione dei thread.  Operatore sincronizzato - 3A proposito! Nelle lezioni del corso hai già visto esempi di sincronizzato, ma sembravano diversi:
public void swap()
{
   synchronized (this)
   {
       //...method logic
   }
}
L'argomento è nuovo per te e ovviamente all'inizio ci sarà confusione con la sintassi. Ricordatelo quindi subito per non confondervi successivamente nei metodi di scrittura. Questi due metodi di scrittura significano la stessa cosa:
public void swap() {

   synchronized (this)
   {
       //...method logic
   }
}


public synchronized void swap() {

   }
}
Nel primo caso si crea un blocco di codice sincronizzato subito dopo l'inserimento del metodo. Viene sincronizzato per oggetto this, ovvero per l'oggetto corrente. E nel secondo esempio metti la parola sincronizzato sull'intero metodo. Non è più necessario indicare esplicitamente l'eventuale oggetto su cui viene effettuata la sincronizzazione. Una volta contrassegnato con una parola un intero metodo, questo metodo verrà automaticamente sincronizzato per tutti gli oggetti della classe. Non approfondiamo le discussioni su quale metodo sia migliore. Per ora, scegli quello che ti piace di più :) La cosa principale è ricordare: puoi dichiarare un metodo sincronizzato solo quando tutta la logica al suo interno viene eseguita da un thread contemporaneamente. Ad esempio, in questo caso doSomething()sarebbe un errore rendere il metodo sincronizzato:
public class Main {

   private Object obj = new Object();

   public void doSomething() {

       //...some logic available to all threads

       synchronized (obj) {

           //logic that is only available to one thread at a time
       }
   }
}
Come puoi vedere, una parte del metodo contiene logica per la quale non è richiesta la sincronizzazione. Il codice in esso contenuto può essere eseguito da più thread contemporaneamente e tutte le posizioni critiche sono allocate in un blocco sincronizzato separato. E un momento. Diamo un'occhiata al microscopio al nostro esempio tratto dalla lezione con lo scambio di nomi:
public void swap()
{
   synchronized (this)
   {
       //...method logic
   }
}
Nota: la sincronizzazione viene eseguita utilizzando this. Cioè, per un oggetto specifico MyClass. Immagina di avere 2 thread ( Thread-1e Thread-2) e un solo oggetto MyClass myClass. In questo caso, se Thread-1il metodo viene chiamato myClass.swap(), il mutex dell'oggetto sarà occupato e Thread-2quando proverai a chiamarlo, myClass.swap()si bloccherà in attesa che il mutex si liberi. Se abbiamo 2 thread e 2 oggetti MyClass- myClass1e myClass2- su oggetti diversi, i nostri thread possono facilmente eseguire simultaneamente metodi sincronizzati. Il primo thread fa:
myClass1.swap();
Il secondo fa:
myClass2.swap();
In questo caso la parola chiave sincronizzata all'interno del metodo swap()non influirà sul funzionamento del programma, poiché la sincronizzazione viene effettuata su un oggetto specifico. E in quest'ultimo caso abbiamo 2 oggetti, quindi i thread non si creano problemi a vicenda. Dopotutto, due oggetti hanno 2 mutex diversi e la loro cattura non dipende l'uno dall'altro.

Caratteristiche di sincronizzazione nei metodi statici

Ma cosa succede se è necessario sincronizzare un metodo statico?
class MyClass {
   private static String name1 = "Olya";
   private static String name2 = "Lena";

   public static synchronized void swap() {
       String s = name1;
       name1 = name2;
       name2 = s;
   }

}
Non è chiaro cosa servirà da mutex in questo caso. Dopotutto, abbiamo già deciso che ogni oggetto ha un mutex. Ma il problema è che per chiamare un metodo statico MyClass.swap()non servono oggetti: il metodo è statico! Allora, qual è il prossimo passo? :/ In realtà, non c'è nessun problema con questo. I creatori di Java si sono occupati di tutto :) Se il metodo che contiene la logica critica "multithread" è statico, la sincronizzazione verrà eseguita per classe. Per maggiore chiarezza il codice sopra riportato può essere riscritto come:
class MyClass {
   private static String name1 = "Olya";
   private static String name2 = "Lena";

   public static void swap() {

       synchronized (MyClass.class) {
           String s = name1;
           name1 = name2;
           name2 = s;
       }
   }

}
In linea di principio avresti potuto pensarci da solo: poiché non ci sono oggetti, il meccanismo di sincronizzazione deve in qualche modo essere “cablato” nelle classi stesse. È così: puoi anche sincronizzarti tra classi diverse.
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION