Java - Sincronizzazione dei thread

Quando avviamo due o più thread all'interno di un programma, potrebbe verificarsi una situazione in cui più thread tentano di accedere alla stessa risorsa e alla fine possono produrre risultati imprevisti a causa di problemi di concorrenza. Ad esempio, se più thread tentano di scrivere all'interno di uno stesso file, potrebbero danneggiare i dati perché uno dei thread può sovrascrivere i dati o mentre un thread sta aprendo lo stesso file nello stesso momento, un altro thread potrebbe chiudere lo stesso file.

Quindi è necessario sincronizzare l'azione di più thread e assicurarsi che solo un thread possa accedere alla risorsa in un dato momento. Questo viene implementato utilizzando un concetto chiamatomonitors. Ogni oggetto in Java è associato a un monitor, che un thread può bloccare o sbloccare. Solo un thread alla volta può mantenere un blocco su un monitor.

Il linguaggio di programmazione Java fornisce un modo molto pratico per creare thread e sincronizzare le loro attività utilizzando synchronizedblocchi. Mantieni le risorse condivise all'interno di questo blocco. Di seguito è riportata la forma generale dell'istruzione sincronizzata:

Sintassi

synchronized(objectidentifier) {
   // Access shared variables and other shared resources
}

Qui, il objectidentifierè un riferimento a un oggetto il cui blocco è associato al monitor rappresentato dall'istruzione synchronized. Ora vedremo due esempi, in cui stamperemo un contatore utilizzando due thread diversi. Quando i thread non sono sincronizzati, stampano il valore del contatore che non è in sequenza, ma quando stampiamo il contatore inserendo il blocco synchronized (), stampa il contatore molto in sequenza per entrambi i thread.

Esempio di multithreading senza sincronizzazione

Ecco un semplice esempio che può o non può stampare il valore del contatore in sequenza e ogni volta che lo eseguiamo, produce un risultato diverso in base alla disponibilità della CPU per un thread.

Esempio

class PrintDemo {
   public void printCount() {
      try {
         for(int i = 5; i > 0; i--) {
            System.out.println("Counter   ---   "  + i );
         }
      } catch (Exception e) {
         System.out.println("Thread  interrupted.");
      }
   }
}

class ThreadDemo extends Thread {
   private Thread t;
   private String threadName;
   PrintDemo  PD;

   ThreadDemo( String name,  PrintDemo pd) {
      threadName = name;
      PD = pd;
   }
   
   public void run() {
      PD.printCount();
      System.out.println("Thread " +  threadName + " exiting.");
   }

   public void start () {
      System.out.println("Starting " +  threadName );
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}

public class TestThread {
   public static void main(String args[]) {

      PrintDemo PD = new PrintDemo();

      ThreadDemo T1 = new ThreadDemo( "Thread - 1 ", PD );
      ThreadDemo T2 = new ThreadDemo( "Thread - 2 ", PD );

      T1.start();
      T2.start();

      // wait for threads to end
         try {
         T1.join();
         T2.join();
      } catch ( Exception e) {
         System.out.println("Interrupted");
      }
   }
}

Questo produce un risultato diverso ogni volta che esegui questo programma -

Produzione

Starting Thread - 1
Starting Thread - 2
Counter   ---   5
Counter   ---   4
Counter   ---   3
Counter   ---   5
Counter   ---   2
Counter   ---   1
Counter   ---   4
Thread Thread - 1  exiting.
Counter   ---   3
Counter   ---   2
Counter   ---   1
Thread Thread - 2  exiting.

Esempio di multithreading con sincronizzazione

Ecco lo stesso esempio che stampa il valore del contatore in sequenza e ogni volta che lo eseguiamo, produce lo stesso risultato.

Esempio

class PrintDemo {
   public void printCount() {
      try {
         for(int i = 5; i > 0; i--) {
            System.out.println("Counter   ---   "  + i );
         }
      } catch (Exception e) {
         System.out.println("Thread  interrupted.");
      }
   }
}

class ThreadDemo extends Thread {
   private Thread t;
   private String threadName;
   PrintDemo  PD;

   ThreadDemo( String name,  PrintDemo pd) {
      threadName = name;
      PD = pd;
   }
   
   public void run() {
      synchronized(PD) {
         PD.printCount();
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }

   public void start () {
      System.out.println("Starting " +  threadName );
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}

public class TestThread {

   public static void main(String args[]) {
      PrintDemo PD = new PrintDemo();

      ThreadDemo T1 = new ThreadDemo( "Thread - 1 ", PD );
      ThreadDemo T2 = new ThreadDemo( "Thread - 2 ", PD );

      T1.start();
      T2.start();

      // wait for threads to end
      try {
         T1.join();
         T2.join();
      } catch ( Exception e) {
         System.out.println("Interrupted");
      }
   }
}

Questo produce lo stesso risultato ogni volta che esegui questo programma -

Produzione

Starting Thread - 1
Starting Thread - 2
Counter   ---   5
Counter   ---   4
Counter   ---   3
Counter   ---   2
Counter   ---   1
Thread Thread - 1  exiting.
Counter   ---   5
Counter   ---   4
Counter   ---   3
Counter   ---   2
Counter   ---   1
Thread Thread - 2  exiting.