Concorrenza Java - Guida rapida

Java è un linguaggio di programmazione multi-thread che significa che possiamo sviluppare programmi multi-thread utilizzando Java. Un programma multi-thread contiene due o più parti che possono essere eseguite contemporaneamente e ciascuna parte può gestire un'attività diversa allo stesso tempo, facendo un uso ottimale delle risorse disponibili specialmente quando il tuo computer ha più CPU.

Per definizione, il multitasking è quando più processi condividono risorse di elaborazione comuni come una CPU. Il multi-threading estende l'idea del multitasking in applicazioni in cui è possibile suddividere operazioni specifiche all'interno di una singola applicazione in singoli thread. Ciascuno dei thread può essere eseguito in parallelo. Il sistema operativo divide il tempo di elaborazione non solo tra le diverse applicazioni, ma anche tra ogni thread all'interno di un'applicazione.

Il multi-threading consente di scrivere in un modo in cui più attività possono procedere contemporaneamente nello stesso programma.

Ciclo di vita di un filo

Un filo attraversa varie fasi del suo ciclo di vita. Ad esempio, un thread nasce, avviato, viene eseguito e quindi muore. Il diagramma seguente mostra il ciclo di vita completo di un thread.

Di seguito sono riportate le fasi del ciclo di vita:

  • New- Un nuovo thread inizia il suo ciclo di vita nel nuovo stato. Rimane in questo stato finché il programma non avvia il thread. Viene anche chiamatoborn thread.

  • Runnable- Dopo l'avvio di un thread appena nato, il thread diventa eseguibile. Si considera che un thread in questo stato stia eseguendo la sua attività.

  • Waiting- A volte, un thread passa allo stato di attesa mentre il thread attende che un altro thread esegua un'attività. Un thread torna allo stato eseguibile solo quando un altro thread segnala al thread in attesa di continuare l'esecuzione.

  • Timed Waiting- Un thread eseguibile può entrare nello stato di attesa a tempo per un intervallo di tempo specificato. Un thread in questo stato torna allo stato eseguibile quando scade l'intervallo di tempo o quando si verifica l'evento che è in attesa.

  • Terminated (Dead) - Un thread eseguibile entra nello stato terminato quando completa la sua attività o termina in altro modo.

Priorità thread

Ogni thread Java ha una priorità che aiuta il sistema operativo a determinare l'ordine in cui sono pianificati i thread.

Le priorità dei thread Java sono nell'intervallo tra MIN_PRIORITY (una costante di 1) e MAX_PRIORITY (una costante di 10). Per impostazione predefinita, a ogni thread viene assegnata la priorità NORM_PRIORITY (una costante di 5).

I thread con priorità più alta sono più importanti per un programma e dovrebbero essere allocati tempo processore prima dei thread con priorità più bassa. Tuttavia, le priorità dei thread non possono garantire l'ordine in cui vengono eseguiti i thread e sono molto dipendenti dalla piattaforma.

Crea un thread implementando un'interfaccia eseguibile

Se la tua classe è concepita per essere eseguita come thread, puoi ottenere ciò implementando un file Runnableinterfaccia. Dovrai seguire tre passaggi fondamentali:

Passo 1

Come primo passo, è necessario implementare un metodo run () fornito da un file Runnableinterfaccia. Questo metodo fornisce un punto di ingresso per il thread e inserirai la tua logica aziendale completa all'interno di questo metodo. Di seguito è riportata una semplice sintassi del metodo run ():

public void run( )

Passo 2

Come secondo passaggio, istanzerai un file Thread oggetto utilizzando il seguente costruttore:

Thread(Runnable threadObj, String threadName);

Dove, threadObj è un'istanza di una classe che implementa ilRunnable interfaccia e threadName è il nome dato al nuovo thread.

Passaggio 3

Una volta creato un oggetto Thread, puoi avviarlo chiamando start()metodo, che esegue una chiamata al metodo run (). Di seguito è riportata una semplice sintassi del metodo start ():

void start();

Example

Ecco un esempio che crea un nuovo thread e inizia a eseguirlo:

class RunnableDemo implements Runnable {
   private Thread t;
   private String threadName;

   RunnableDemo(String name) {
      threadName = name;
      System.out.println("Creating " +  threadName );
   }
   
   public void run() {
      System.out.println("Running " +  threadName );
      
      try {
      
         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            
            // Let the thread sleep for a while.
            Thread.sleep(50);
         }
      } catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      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[]) {
      RunnableDemo R1 = new RunnableDemo("Thread-1");
      R1.start();
      
      RunnableDemo R2 = new RunnableDemo("Thread-2");
      R2.start();
   }   
}

Questo produrrà il seguente risultato:

Output

Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.

Creare un thread estendendo una classe di thread

Il secondo modo per creare un thread è creare una nuova classe che si estende Threadclasse utilizzando i seguenti due semplici passaggi. Questo approccio fornisce una maggiore flessibilità nella gestione di più thread creati utilizzando i metodi disponibili nella classe Thread.

Passo 1

Dovrai eseguire l'override run( )metodo disponibile nella classe Thread. Questo metodo fornisce un punto di ingresso per il thread e inserirai la tua logica aziendale completa all'interno di questo metodo. Di seguito è riportata una semplice sintassi del metodo run ():

public void run( )

Passo 2

Una volta creato l'oggetto Thread, puoi avviarlo chiamando start()metodo, che esegue una chiamata al metodo run (). Di seguito è riportata una semplice sintassi del metodo start ():

void start( );

Example

Ecco il programma precedente riscritto per estendere il thread -

class ThreadDemo extends Thread {
   private Thread t;
   private String threadName;
   
   ThreadDemo(String name) {
      threadName = name;
      System.out.println("Creating " +  threadName );
   }
   
   public void run() {
      System.out.println("Running " +  threadName );
      
      try {

         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            
            // Let the thread sleep for a while.
            Thread.sleep(50);
         }
      } catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      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[]) {
      ThreadDemo T1 = new ThreadDemo("Thread-1");
      T1.start();
      
      ThreadDemo T2 = new ThreadDemo("Thread-2");
      T2.start();
   }   
}

Questo produrrà il seguente risultato:

Output

Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.

In questo capitolo discuteremo i diversi aspetti della configurazione di un ambiente congeniale per Java.

Configurazione dell'ambiente locale

Se sei ancora disposto a configurare il tuo ambiente per il linguaggio di programmazione Java, questa sezione ti guiderà su come scaricare e configurare Java sulla tua macchina. Di seguito sono riportati i passaggi per configurare l'ambiente.

Java SE è disponibile gratuitamente dal collegamento Scarica Java . Puoi scaricare una versione basata sul tuo sistema operativo.

Segui le istruzioni per scaricare Java ed eseguire il file .exeper installare Java sulla tua macchina. Una volta installato Java sulla macchina, sarà necessario impostare le variabili di ambiente in modo che puntino alle directory di installazione corrette -

Configurazione del percorso per Windows

Supponendo che tu abbia installato Java nella directory c: \ Program Files \ java \ jdk -

  • Fare clic con il pulsante destro del mouse su "Risorse del computer" e selezionare "Proprietà".

  • Fare clic sul pulsante "Variabili d'ambiente" nella scheda "Avanzate".

  • Ora, modifica la variabile "Path" in modo che contenga anche il percorso dell'eseguibile Java. Ad esempio, se il percorso è attualmente impostato su "C: \ WINDOWS \ SYSTEM32", modificare il percorso in "C: \ WINDOWS \ SYSTEM32; c: \ Program Files \ java \ jdk \ bin".

Configurazione del percorso per Linux, UNIX, Solaris, FreeBSD

La variabile d'ambiente PATH dovrebbe essere impostata in modo che punti a dove sono stati installati i binari Java. Fare riferimento alla documentazione della shell, se si hanno problemi a farlo.

Esempio, se usi bash come shell, aggiungi la seguente riga alla fine di '.bashrc: export PATH = / path / to / java: $ PATH'

Editor Java popolari

Per scrivere i tuoi programmi Java, avrai bisogno di un editor di testo. Sul mercato sono disponibili IDE ancora più sofisticati. Ma per ora, puoi considerare uno dei seguenti:

  • Notepad - Su macchina Windows, puoi utilizzare qualsiasi semplice editor di testo come Blocco note (consigliato per questo tutorial), TextPad.

  • Netbeans - Un IDE Java che è open-source e gratuito che può essere scaricato da https://netbeans.org/index.html.

  • Eclipse - Un IDE Java sviluppato dalla comunità open source di eclipse e può essere scaricato da https://www.eclipse.org/.

Core Java fornisce il controllo completo sul programma multithread. È possibile sviluppare un programma multithread che può essere sospeso, ripreso o interrotto completamente in base alle proprie esigenze. Esistono vari metodi statici che è possibile utilizzare sugli oggetti thread per controllarne il comportamento. La tabella seguente elenca questi metodi:

Sr.No. Metodo e descrizione
1

public void suspend()

Questo metodo mette un thread nello stato sospeso e può essere ripreso utilizzando il metodo resume ().

2

public void stop()

Questo metodo interrompe completamente un thread.

3

public void resume()

Questo metodo riprende un thread, che è stato sospeso utilizzando il metodo suspend ().

4

public void wait()

Fa in modo che il thread corrente attenda fino a quando un altro thread invoca notifica ().

5

public void notify()

Riattiva un singolo thread in attesa sul monitor di questo oggetto.

Tieni presente che le ultime versioni di Java hanno deprecato l'utilizzo dei metodi suspend (), resume () e stop () e quindi è necessario utilizzare le alternative disponibili.

Esempio

class RunnableDemo implements Runnable {
   public Thread t;
   private String threadName;
   boolean suspended = false;

   RunnableDemo(String name) {
      threadName = name;
      System.out.println("Creating " +  threadName );
   }
   
   public void run() {
      System.out.println("Running " +  threadName );

      try {
         
         for(int i = 10; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);

            // Let the thread sleep for a while.
            Thread.sleep(300);

            synchronized(this) {
               
               while(suspended) {
                  wait();
               }
            }
         }
      } catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }

   public void start () {
      System.out.println("Starting " +  threadName );
      
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
   
   void suspend() {
      suspended = true;
   }
   
   synchronized void resume() {
      suspended = false;
      notify();
   }
}

public class TestThread {

   public static void main(String args[]) {
      RunnableDemo R1 = new RunnableDemo("Thread-1");
      R1.start();

      RunnableDemo R2 = new RunnableDemo("Thread-2");
      R2.start();

      try {
         Thread.sleep(1000);
         R1.suspend();
         System.out.println("Suspending First Thread");
         Thread.sleep(1000);
         R1.resume();
         System.out.println("Resuming First Thread");
         
         R2.suspend();
         System.out.println("Suspending thread Two");
         Thread.sleep(1000);
         R2.resume();
         System.out.println("Resuming thread Two");
      } catch (InterruptedException e) {
         System.out.println("Main thread Interrupted");
      } try {
         System.out.println("Waiting for threads to finish.");
         R1.t.join();
         R2.t.join();
      } catch (InterruptedException e) {
         System.out.println("Main thread Interrupted");
      }
      System.out.println("Main thread exiting.");
   }
}

Il programma precedente produce il seguente output:

Produzione

Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 10
Running Thread-2
Thread: Thread-2, 10
Thread: Thread-1, 9
Thread: Thread-2, 9
Thread: Thread-1, 8
Thread: Thread-2, 8
Thread: Thread-1, 7
Thread: Thread-2, 7
Suspending First Thread
Thread: Thread-2, 6
Thread: Thread-2, 5
Thread: Thread-2, 4
Resuming First Thread
Suspending thread Two
Thread: Thread-1, 6
Thread: Thread-1, 5
Thread: Thread-1, 4
Thread: Thread-1, 3
Resuming thread Two
Thread: Thread-2, 3
Waiting for threads to finish.
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.
Main thread exiting.

Se sei a conoscenza della comunicazione interprocesso, sarà facile per te capire la comunicazione interthread. La comunicazione tra thread è importante quando si sviluppa un'applicazione in cui due o più thread si scambiano alcune informazioni.

Ci sono tre semplici metodi e un piccolo trucco che rende possibile la comunicazione con i thread. Tutti e tre i metodi sono elencati di seguito:

Sr.No. Metodo e descrizione
1

public void wait()

Fa in modo che il thread corrente attenda fino a quando un altro thread invoca notifica ().

2

public void notify()

Riattiva un singolo thread in attesa sul monitor di questo oggetto.

3

public void notifyAll()

Riattiva tutti i thread che hanno chiamato wait () sullo stesso oggetto.

Questi metodi sono stati implementati come finalmetodi in Object, quindi sono disponibili in tutte le classi. Tutti e tre i metodi possono essere chiamati solo dall'interno di un filesynchronized contesto.

Esempio

Questo esempio mostra come due thread possono comunicare utilizzando wait() e notify()metodo. È possibile creare un sistema complesso utilizzando lo stesso concetto.

class Chat {
   boolean flag = false;

   public synchronized void Question(String msg) {

      if (flag) {
         
         try {
            wait();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
      System.out.println(msg);
      flag = true;
      notify();
   }

   public synchronized void Answer(String msg) {

      if (!flag) {
         
         try {
            wait();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
      System.out.println(msg);
      flag = false;
      notify();
   }
}

class T1 implements Runnable {
   Chat m;
   String[] s1 = { "Hi", "How are you ?", "I am also doing fine!" };

   public T1(Chat m1) {
      this.m = m1;
      new Thread(this, "Question").start();
   }

   public void run() {
   
      for (int i = 0; i < s1.length; i++) {
         m.Question(s1[i]);
      }
   }
}

class T2 implements Runnable {
   Chat m;
   String[] s2 = { "Hi", "I am good, what about you?", "Great!" };

   public T2(Chat m2) {
      this.m = m2;
      new Thread(this, "Answer").start();
   }

   public void run() {

      for (int i = 0; i < s2.length; i++) {
         m.Answer(s2[i]);
      }
   }
}

public class TestThread {

   public static void main(String[] args) {
      Chat m = new Chat();
      new T1(m);
      new T2(m);
   }
}

Quando il programma di cui sopra viene rispettato ed eseguito, produce il seguente risultato:

Produzione

Hi
Hi
How are you ?
I am good, what about you?
I am also doing fine!
Great!

L'esempio sopra è stato preso e poi modificato da [https://stackoverflow.com/questions/2170520/inter-thread-communication-in-java]

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.

Deadlock descrive una situazione in cui due o più thread sono bloccati per sempre, in attesa l'uno dell'altro. Il deadlock si verifica quando più thread richiedono gli stessi blocchi ma li ottengono in un ordine diverso. Un programma multithread Java potrebbe soffrire della condizione di deadlock perché il filesynchronizedla parola chiave fa sì che il thread in esecuzione si blocchi durante l'attesa del blocco, o monitoraggio, associato all'oggetto specificato. Ecco un esempio.

Esempio

public class TestThread {
   public static Object Lock1 = new Object();
   public static Object Lock2 = new Object();
   
   public static void main(String args[]) {
      ThreadDemo1 T1 = new ThreadDemo1();
      ThreadDemo2 T2 = new ThreadDemo2();
      T1.start();
      T2.start();
   }
   
   private static class ThreadDemo1 extends Thread {
   
      public void run() {
      
         synchronized (Lock1) {
            System.out.println("Thread 1: Holding lock 1...");

            try {
               Thread.sleep(10);
            } catch (InterruptedException e) {}
            System.out.println("Thread 1: Waiting for lock 2...");

            synchronized (Lock2) {
               System.out.println("Thread 1: Holding lock 1 & 2...");
            }
         }
      }
   }

   private static class ThreadDemo2 extends Thread {
   
      public void run() {
      
         synchronized (Lock2) {
            System.out.println("Thread 2: Holding lock 2...");
            
            try {
               Thread.sleep(10);
            } catch (InterruptedException e) {}
            System.out.println("Thread 2: Waiting for lock 1...");
            
            synchronized (Lock1) {
               System.out.println("Thread 2: Holding lock 1 & 2...");
            }
         }
      }
   } 
}

Quando compili ed esegui il programma sopra, trovi una situazione di deadlock e il seguente è l'output prodotto dal programma -

Produzione

Thread 1: Holding lock 1...
Thread 2: Holding lock 2...
Thread 1: Waiting for lock 2...
Thread 2: Waiting for lock 1...

Il programma sopra si bloccherà per sempre perché nessuno dei thread è in posizione per procedere e aspetta che l'altro rilasci il blocco, quindi puoi uscire dal programma premendo CTRL + C.

Esempio di soluzione deadlock

Cambiamo l'ordine del blocco e l'esecuzione dello stesso programma per vedere se entrambi i thread si aspettano ancora l'uno per l'altro -

Esempio

public class TestThread {
   public static Object Lock1 = new Object();
   public static Object Lock2 = new Object();
   
   public static void main(String args[]) {
      ThreadDemo1 T1 = new ThreadDemo1();
      ThreadDemo2 T2 = new ThreadDemo2();
      T1.start();
      T2.start();
   }
   
   private static class ThreadDemo1 extends Thread {
   
      public void run() {
         
         synchronized (Lock1) {
            System.out.println("Thread 1: Holding lock 1...");
            
            try {
               Thread.sleep(10);
            } catch (InterruptedException e) {}
            System.out.println("Thread 1: Waiting for lock 2...");

            synchronized (Lock2) {
               System.out.println("Thread 1: Holding lock 1 & 2...");
            }
         }
      }
   }

   private static class ThreadDemo2 extends Thread {
      
      public void run() {
         
         synchronized (Lock1) {
            System.out.println("Thread 2: Holding lock 1...");
           
            try {
               Thread.sleep(10);
            } catch (InterruptedException e) {}
            System.out.println("Thread 2: Waiting for lock 2...");

            synchronized (Lock2) {
               System.out.println("Thread 2: Holding lock 1 & 2...");
            }
         }
      }
   } 
}

Quindi la semplice modifica dell'ordine dei blocchi impedisce al programma di entrare in una situazione di deadlock e si completa con il seguente risultato:

Produzione

Thread 1: Holding lock 1...
Thread 1: Waiting for lock 2...
Thread 1: Holding lock 1 & 2...
Thread 2: Holding lock 1...
Thread 2: Waiting for lock 2...
Thread 2: Holding lock 1 & 2...

L'esempio sopra è solo per chiarire il concetto, tuttavia, è un concetto complesso e dovresti approfondirlo prima di sviluppare le tue applicazioni per affrontare situazioni di deadlock.

La classe ThreadLocal viene utilizzata per creare variabili locali di thread che possono essere lette e scritte solo dallo stesso thread. Ad esempio, se due thread accedono al codice che fa riferimento alla stessa variabile threadLocal, ogni thread non vedrà alcuna modifica alla variabile threadLocal eseguita da un altro thread.

Metodi ThreadLocal

Di seguito è riportato l'elenco dei metodi importanti disponibili nella classe ThreadLocal.

Sr.No. Metodo e descrizione
1

public T get()

Restituisce il valore nella copia del thread corrente di questa variabile locale del thread.

2

protected T initialValue()

Restituisce il "valore iniziale" del thread corrente per questa variabile locale del thread.

3

public void remove()

Rimuove il valore del thread corrente per questa variabile locale del thread.

4

public void set(T value)

Imposta la copia del thread corrente di questa variabile locale del thread sul valore specificato.

Esempio

Il seguente programma TestThread mostra alcuni di questi metodi della classe ThreadLocal. Qui abbiamo usato due variabili contatore, una è normale e un'altra è ThreadLocal.

class RunnableDemo implements Runnable {
   int counter;
   ThreadLocal<Integer> threadLocalCounter = new ThreadLocal<Integer>();

   public void run() {     
      counter++;

      if(threadLocalCounter.get() != null) {
         threadLocalCounter.set(threadLocalCounter.get().intValue() + 1);
      } else {
         threadLocalCounter.set(0);
      }
      System.out.println("Counter: " + counter);
      System.out.println("threadLocalCounter: " + threadLocalCounter.get());
   }
}

public class TestThread {

   public static void main(String args[]) {
      RunnableDemo commonInstance = new RunnableDemo();

      Thread t1 = new Thread(commonInstance);
      Thread t2 = new Thread(commonInstance);
      Thread t3 = new Thread(commonInstance);
      Thread t4 = new Thread(commonInstance);

      t1.start();
      t2.start();
      t3.start();
      t4.start();

      // wait for threads to end
      try {
         t1.join();
         t2.join();
         t3.join();
         t4.join();
      } catch (Exception e) {
         System.out.println("Interrupted");
      }
   }
}

Questo produrrà il seguente risultato.

Produzione

Counter: 1
threadLocalCounter: 0
Counter: 2
threadLocalCounter: 0
Counter: 3
threadLocalCounter: 0
Counter: 4
threadLocalCounter: 0

Puoi vedere che il valore di counter viene aumentato da ogni thread, ma threadLocalCounter rimane 0 per ogni thread.

Un java.util.concurrent.ThreadLocalRandom è una classe di utilità introdotta da jdk 1.7 in poi ed è utile quando sono necessari più thread o ForkJoinTask per generare numeri casuali. Migliora le prestazioni e ha meno contese rispetto al metodo Math.random ().

Metodi ThreadLocalRandom

Di seguito è riportato l'elenco dei metodi importanti disponibili nella classe ThreadLocalRandom.

Sr.No. Metodo e descrizione
1

public static ThreadLocalRandom current()

Restituisce ThreadLocalRandom del thread corrente.

2

protected int next(int bits)

Genera il numero pseudocasuale successivo.

3

public double nextDouble(double n)

Restituisce un valore double pseudocasuale, uniformemente distribuito tra 0 (incluso) e il valore specificato (esclusivo).

4

public double nextDouble(double least, double bound)

Restituisce un valore pseudocasuale, distribuito uniformemente tra il valore minimo specificato (incluso) e il limite (esclusivo).

5

public int nextInt(int least, int bound)

Restituisce un valore pseudocasuale, distribuito uniformemente tra il valore minimo specificato (incluso) e il limite (esclusivo).

6

public long nextLong(long n)

Restituisce un valore pseudocasuale, distribuito uniformemente tra 0 (incluso) e il valore specificato (esclusivo).

7

public long nextLong(long least, long bound)

Restituisce un valore pseudocasuale, distribuito uniformemente tra il valore minimo specificato (incluso) e il limite (esclusivo).

8

public void setSeed(long seed)

Genera UnsupportedOperationException.

Esempio

Il seguente programma TestThread mostra alcuni di questi metodi dell'interfaccia Lock. Qui abbiamo usato lock () per acquisire il lock e unlock () per rilasciare il lock.

import java.util.Random;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.ThreadLocalRandom;

public class TestThread {
  
   public static void main(final String[] arguments) {
      System.out.println("Random Integer: " + new Random().nextInt());  
      System.out.println("Seeded Random Integer: " + new Random(15).nextInt());  
      System.out.println(
         "Thread Local Random Integer: " + ThreadLocalRandom.current().nextInt());
      
      final ThreadLocalRandom random = ThreadLocalRandom.current();  
      random.setSeed(15); //exception will come as seeding is not allowed in ThreadLocalRandom.
      System.out.println("Seeded Thread Local Random Integer: " + random.nextInt());  
   }
}

Questo produrrà il seguente risultato.

Produzione

Random Integer: 1566889198
Seeded Random Integer: -1159716814
Thread Local Random Integer: 358693993
Exception in thread "main" java.lang.UnsupportedOperationException
        at java.util.concurrent.ThreadLocalRandom.setSeed(Unknown Source)
        at TestThread.main(TestThread.java:21)

Qui abbiamo usato le classi ThreadLocalRandom e Random per ottenere numeri casuali.

Un'interfaccia java.util.concurrent.locks.Lock viene utilizzata come meccanismo di sincronizzazione dei thread simile ai blocchi sincronizzati. Il nuovo meccanismo di blocco è più flessibile e fornisce più opzioni rispetto a un blocco sincronizzato. Le principali differenze tra un blocco e un blocco sincronizzato sono le seguenti:

  • Guarantee of sequence- Il blocco sincronizzato non fornisce alcuna garanzia di sequenza in cui verrà concesso l'accesso al thread in attesa. L'interfaccia di blocco lo gestisce.

  • No timeout- Il blocco sincronizzato non ha alcuna opzione di timeout se il blocco non è concesso. L'interfaccia di blocco fornisce tale opzione.

  • Single method - Il blocco sincronizzato deve essere contenuto completamente in un unico metodo, mentre i metodi lock () e unlock () di un'interfaccia di blocco possono essere chiamati con metodi differenti.

Metodi di blocco

Di seguito è riportato l'elenco dei metodi importanti disponibili nella classe Lock.

Sr.No. Metodo e descrizione
1

public void lock()

Acquisisce il blocco.

2

public void lockInterruptibly()

Acquisisce il blocco a meno che il thread corrente non venga interrotto.

3

public Condition newCondition()

Restituisce una nuova istanza di condizione associata a questa istanza di blocco.

4

public boolean tryLock()

Acquisisce il blocco solo se è libero al momento dell'invocazione.

5

public boolean tryLock()

Acquisisce il blocco solo se è libero al momento dell'invocazione.

6

public boolean tryLock(long time, TimeUnit unit)

Acquisisce il blocco se è libero entro il tempo di attesa specificato e il thread corrente non è stato interrotto.

7

public void unlock()

Rilascia il blocco.

Esempio

Il seguente programma TestThread mostra alcuni di questi metodi dell'interfaccia Lock. Qui abbiamo usato lock () per acquisire il lock e unlock () per rilasciare il lock.

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class PrintDemo {
   private final Lock queueLock = new ReentrantLock();

   public void print() {
      queueLock.lock();

      try {
         Long duration = (long) (Math.random() * 10000);
         System.out.println(Thread.currentThread().getName() 
            + "  Time Taken " + (duration / 1000) + " seconds.");
         Thread.sleep(duration);
      } catch (InterruptedException e) {
         e.printStackTrace();
      } finally {
         System.out.printf(
            "%s printed the document successfully.\n", Thread.currentThread().getName());
         queueLock.unlock();
      }
   }
}

class ThreadDemo extends Thread {
   PrintDemo  printDemo;

   ThreadDemo(String name,  PrintDemo printDemo) {
      super(name);
      this.printDemo = printDemo;
   }   

   @Override
   public void run() {
      System.out.printf(
         "%s starts printing a document\n", Thread.currentThread().getName());
      printDemo.print();
   }
}

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);
      ThreadDemo t3 = new ThreadDemo("Thread - 3 ", PD);
      ThreadDemo t4 = new ThreadDemo("Thread - 4 ", PD);

      t1.start();
      t2.start();
      t3.start();
      t4.start();
   }
}

Questo produrrà il seguente risultato.

Produzione

Thread - 1  starts printing a document
Thread - 4  starts printing a document
Thread - 3  starts printing a document
Thread - 2  starts printing a document
Thread - 1   Time Taken 4 seconds.
Thread - 1  printed the document successfully.
Thread - 4   Time Taken 3 seconds.
Thread - 4  printed the document successfully.
Thread - 3   Time Taken 5 seconds.
Thread - 3  printed the document successfully.
Thread - 2   Time Taken 4 seconds.
Thread - 2  printed the document successfully.

Abbiamo usato la classe ReentrantLock come implementazione dell'interfaccia Lock qui. La classe ReentrantLock consente a un thread di bloccare un metodo anche se ha già il blocco su un altro metodo.

Un'interfaccia java.util.concurrent.locks.ReadWriteLock consente la lettura di più thread contemporaneamente, ma solo un thread può scrivere alla volta.

  • Read Lock - Se nessun thread ha bloccato ReadWriteLock per la scrittura, più thread possono accedere al blocco di lettura.

  • Write Lock - Se nessun thread sta leggendo o scrivendo, un thread può accedere al blocco di scrittura.

Metodi di blocco

Di seguito è riportato l'elenco dei metodi importanti disponibili nella classe Lock.

Sr.No. Metodo e descrizione
1

public Lock readLock()

Restituisce il lucchetto utilizzato per la lettura.

2

public Lock writeLock()

Restituisce il blocco utilizzato per la scrittura.

Esempio

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class TestThread {
   private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
   private static String message = "a";

   public static void main(String[] args) throws InterruptedException {
      Thread t1 = new Thread(new WriterA());
      t1.setName("Writer A");
      
      Thread t2 = new Thread(new WriterB());
      t2.setName("Writer B");
      
      Thread t3 = new Thread(new Reader());
      t3.setName("Reader");
      t1.start();
      t2.start();
      t3.start();
      t1.join();
      t2.join();
      t3.join();
   }

   static class Reader implements Runnable {

      public void run() {
         
         if(lock.isWriteLocked()) {
            System.out.println("Write Lock Present.");
         }
         lock.readLock().lock();

         try {
            Long duration = (long) (Math.random() * 10000);
            System.out.println(Thread.currentThread().getName() 
               + "  Time Taken " + (duration / 1000) + " seconds.");
            Thread.sleep(duration);
         } catch (InterruptedException e) {
            e.printStackTrace();
         } finally {
            System.out.println(Thread.currentThread().getName() +": "+ message );
            lock.readLock().unlock();
         }
      }
   }

   static class WriterA implements Runnable {

      public void run() {
         lock.writeLock().lock();
         
         try {
            Long duration = (long) (Math.random() * 10000);
            System.out.println(Thread.currentThread().getName() 
               + "  Time Taken " + (duration / 1000) + " seconds.");
            Thread.sleep(duration);
         } catch (InterruptedException e) {
            e.printStackTrace();
         } finally {
            message = message.concat("a");
            lock.writeLock().unlock();
         }
      }
   }

   static class WriterB implements Runnable {

      public void run() {
         lock.writeLock().lock();
         
         try {
            Long duration = (long) (Math.random() * 10000);
            System.out.println(Thread.currentThread().getName() 
               + "  Time Taken " + (duration / 1000) + " seconds.");
            Thread.sleep(duration);
         } catch (InterruptedException e) {
            e.printStackTrace();
         } finally {
            message = message.concat("b");
            lock.writeLock().unlock();
         }
      }
   }
}

Questo produrrà il seguente risultato.

Produzione

Writer A  Time Taken 6 seconds.
Write Lock Present.
Writer B  Time Taken 2 seconds.
Reader  Time Taken 0 seconds.
Reader: aab

Un'interfaccia java.util.concurrent.locks.Condition fornisce una capacità del thread di sospendere la sua esecuzione, fino a quando la condizione data è vera. Un oggetto Condition è necessariamente associato a un Lock e deve essere ottenuto utilizzando il metodo newCondition ().

Metodi di condizione

Di seguito è riportato l'elenco dei metodi importanti disponibili nella classe Condition.

Sr.No. Metodo e descrizione
1

public void await()

Fa in modo che il thread corrente attenda finché non viene segnalato o interrotto.

2

public boolean await(long time, TimeUnit unit)

Fa in modo che il thread corrente attenda fino a quando non viene segnalato o interrotto o fino allo scadere del tempo di attesa specificato.

3

public long awaitNanos(long nanosTimeout)

Fa in modo che il thread corrente attenda fino a quando non viene segnalato o interrotto o fino allo scadere del tempo di attesa specificato.

4

public long awaitUninterruptibly()

Fa in modo che il thread corrente attenda finché non viene segnalato.

5

public long awaitUntil()

Fa in modo che il thread corrente attenda fino a quando non viene segnalato o interrotto o fino allo scadere della scadenza specificata.

6

public void signal()

Sveglia un thread in attesa.

7

public void signalAll()

Riattiva tutti i thread in attesa.

Esempio

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestThread {

   public static void main(String[] args) throws InterruptedException {
      ItemQueue itemQueue = new ItemQueue(10);

      //Create a producer and a consumer.
      Thread producer = new Producer(itemQueue);
      Thread consumer = new Consumer(itemQueue);

      //Start both threads.
      producer.start();
      consumer.start();

      //Wait for both threads to terminate.
      producer.join();
      consumer.join();
   }

   static class ItemQueue {
      private Object[] items = null;
      private int current = 0;
      private int placeIndex = 0;
      private int removeIndex = 0;

      private final Lock lock;
      private final Condition isEmpty;
      private final Condition isFull;

      public ItemQueue(int capacity) {
         this.items = new Object[capacity];
         lock = new ReentrantLock();
         isEmpty = lock.newCondition();
         isFull = lock.newCondition();
      }

      public void add(Object item) throws InterruptedException {
         lock.lock();

         while(current >= items.length)
            isFull.await();

         items[placeIndex] = item;
         placeIndex = (placeIndex + 1) % items.length;
         ++current;

         //Notify the consumer that there is data available.
         isEmpty.signal();
         lock.unlock();
      }

      public Object remove() throws InterruptedException {
         Object item = null;

         lock.lock();

         while(current <= 0) {
            isEmpty.await();
         }
         item = items[removeIndex];
         removeIndex = (removeIndex + 1) % items.length;
         --current;

         //Notify the producer that there is space available.
         isFull.signal();
         lock.unlock();

         return item;
      }

      public boolean isEmpty() {
         return (items.length == 0);
      }
   }

   static class Producer extends Thread {
      private final ItemQueue queue;
      
      public Producer(ItemQueue queue) {
         this.queue = queue;
      }

      @Override
      public void run() {
         String[] numbers =
            {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"};

         try {
            
            for(String number: numbers) {
               System.out.println("[Producer]: " + number);
            }
            queue.add(null);
         } catch (InterruptedException ex) {
            ex.printStackTrace();
         } 
      }
   }

   static class Consumer extends Thread {
      private final ItemQueue queue;
      
      public Consumer(ItemQueue queue) {
         this.queue = queue;
      }

      @Override
      public void run() {
         
         try {
            
            do {
               Object number = queue.remove();
               System.out.println("[Consumer]: " + number);

               if(number == null) {
                  return;
               }
            } while(!queue.isEmpty());
         } catch (InterruptedException ex) {
            ex.printStackTrace();
         }
      }
   }
}

Questo produrrà il seguente risultato.

Produzione

[Producer]: 1
[Producer]: 2
[Producer]: 3
[Producer]: 4
[Producer]: 5
[Producer]: 6
[Producer]: 7
[Producer]: 8
[Producer]: 9
[Producer]: 10
[Producer]: 11
[Producer]: 12
[Consumer]: null

Una classe java.util.concurrent.atomic.AtomicInteger fornisce operazioni sul valore int sottostante che può essere letto e scritto in modo atomico e contiene anche operazioni atomiche avanzate. AtomicInteger supporta operazioni atomiche sulla variabile int sottostante. Ha metodi get e set che funzionano come letture e scritture su variabili volatili. Cioè, un insieme ha una relazione accade prima con qualsiasi successivo get sulla stessa variabile. Anche il metodo compareAndSet atomico ha queste caratteristiche di coerenza della memoria.

Metodi AtomicInteger

Di seguito è riportato l'elenco dei metodi importanti disponibili nella classe AtomicInteger.

Sr.No. Metodo e descrizione
1

public int addAndGet(int delta)

Atomicamente aggiunge il valore dato al valore corrente.

2

public boolean compareAndSet(int expect, int update)

Atomicamente imposta il valore sul valore aggiornato dato se il valore corrente è uguale al valore atteso.

3

public int decrementAndGet()

Decrementa atomicamente di uno il valore corrente.

4

public double doubleValue()

Restituisce il valore del numero specificato come double.

5

public float floatValue()

Restituisce il valore del numero specificato come float.

6

public int get()

Ottiene il valore corrente.

7

public int getAndAdd(int delta)

Atomiclly aggiunge il valore dato al valore corrente.

8

public int getAndDecrement()

Decrementa atomicamente di uno il valore corrente.

9

public int getAndIncrement()

Atomicamente incrementa di uno il valore corrente.

10

public int getAndSet(int newValue)

Atomicamente imposta il valore dato e restituisce il vecchio valore.

11

public int incrementAndGet()

Atomicamente incrementa di uno il valore corrente.

12

public int intValue()

Restituisce il valore del numero specificato come int.

13

public void lazySet(int newValue)

Alla fine si imposta sul valore dato.

14

public long longValue()

Restituisce il valore del numero specificato come long.

15

public void set(int newValue)

Imposta il valore dato.

16

public String toString()

Restituisce la rappresentazione String del valore corrente.

17

public boolean weakCompareAndSet(int expect, int update)

Atomicamente imposta il valore sul valore aggiornato dato se il valore corrente è uguale al valore atteso.

Esempio

Il seguente programma TestThread mostra un'implementazione non sicura di counter in un ambiente basato su thread.

public class TestThread {

   static class Counter {
      private int c = 0;

      public void increment() {
         c++;
      }

      public int value() {
         return c;
      }
   }
   
   public static void main(final String[] arguments) throws InterruptedException {
      final Counter counter = new Counter();
      
      //1000 threads
      for(int i = 0; i < 1000 ; i++) {
         
         new Thread(new Runnable() {
            
            public void run() {
               counter.increment();
            }
         }).start(); 
      }  
      Thread.sleep(6000);
      System.out.println("Final number (should be 1000): " + counter.value());
   }  
}

Ciò può produrre il seguente risultato a seconda della velocità del computer e dell'interleaving dei thread.

Produzione

Final number (should be 1000): 1000

Esempio

import java.util.concurrent.atomic.AtomicInteger;

public class TestThread {

   static class Counter {
      private AtomicInteger c = new AtomicInteger(0);

      public void increment() {
         c.getAndIncrement();
      }

      public int value() {
         return c.get();
      }
   }
   
   public static void main(final String[] arguments) throws InterruptedException {
      final Counter counter = new Counter();
      
      //1000 threads
      for(int i = 0; i < 1000 ; i++) {

         new Thread(new Runnable() {
            public void run() {
               counter.increment();
            }
         }).start(); 
      }  
      Thread.sleep(6000);
      System.out.println("Final number (should be 1000): " + counter.value());
   }
}

Questo produrrà il seguente risultato.

Produzione

Final number (should be 1000): 1000

Una classe java.util.concurrent.atomic.AtomicLong fornisce operazioni sul valore long sottostante che può essere letto e scritto in modo atomico e contiene anche operazioni atomiche avanzate. AtomicLong supporta operazioni atomiche sulla variabile long sottostante. Ha metodi get e set che funzionano come letture e scritture su variabili volatili. Cioè, un insieme ha una relazione accade prima con qualsiasi successivo get sulla stessa variabile. Anche il metodo compareAndSet atomico ha queste caratteristiche di coerenza della memoria.

Metodi AtomicLong

Di seguito è riportato l'elenco dei metodi importanti disponibili nella classe AtomicLong.

Sr.No. Metodo e descrizione
1

public long addAndGet(long delta)

Atomicamente aggiunge il valore dato al valore corrente.

2

public boolean compareAndSet(long expect, long update)

Atomicamente imposta il valore sul valore aggiornato dato se il valore corrente è uguale al valore atteso.

3

public long decrementAndGet()

Decrementa atomicamente di uno il valore corrente.

4

public double doubleValue()

Restituisce il valore del numero specificato come double.

5

public float floatValue()

Restituisce il valore del numero specificato come float.

6

public long get()

Ottiene il valore corrente.

7

public long getAndAdd(long delta)

Atomiclly aggiunge il valore dato al valore corrente.

8

public long getAndDecrement()

Decrementa atomicamente di uno il valore corrente.

9

public long getAndIncrement()

Atomicamente incrementa di uno il valore corrente.

10

public long getAndSet(long newValue)

Atomicamente imposta il valore dato e restituisce il vecchio valore.

11

public long incrementAndGet()

Atomicamente incrementa di uno il valore corrente.

12

public int intValue()

Restituisce il valore del numero specificato come int.

13

public void lazySet(long newValue)

Alla fine si imposta sul valore dato.

14

public long longValue()

Restituisce il valore del numero specificato come long.

15

public void set(long newValue)

Imposta il valore dato.

16

public String toString()

Restituisce la rappresentazione String del valore corrente.

17

public boolean weakCompareAndSet(long expect, long update)

Atomicamente imposta il valore sul valore aggiornato dato se il valore corrente è uguale al valore atteso.

Esempio

Il seguente programma TestThread mostra un'implementazione sicura del contatore utilizzando AtomicLong in un ambiente basato su thread.

import java.util.concurrent.atomic.AtomicLong;

public class TestThread {

   static class Counter {
      private AtomicLong c = new AtomicLong(0);

      public void increment() {
         c.getAndIncrement();
      }

      public long value() {
         return c.get();
      }
   }

   public static void main(final String[] arguments) throws InterruptedException {
      final Counter counter = new Counter();
      
      //1000 threads
      for(int i = 0; i < 1000 ; i++) {
         
         new Thread(new Runnable() {
            
            public void run() {
               counter.increment();
            }
         }).start();	
      }
      Thread.sleep(6000);			   		  
      System.out.println("Final number (should be 1000): " + counter.value());
   }
}

Questo produrrà il seguente risultato.

Produzione

Final number (should be 1000): 1000

Una classe java.util.concurrent.atomic.AtomicBoolean fornisce operazioni sul valore booleano sottostante che può essere letto e scritto in modo atomico e contiene anche operazioni atomiche avanzate. AtomicBoolean supporta operazioni atomiche sulla variabile booleana sottostante. Ha metodi get e set che funzionano come letture e scritture su variabili volatili. Cioè, un insieme ha una relazione accade prima con qualsiasi successivo get sulla stessa variabile. Anche il metodo compareAndSet atomico ha queste caratteristiche di coerenza della memoria.

Metodi AtomicBoolean

Di seguito è riportato l'elenco dei metodi importanti disponibili nella classe AtomicBoolean.

Sr.No. Metodo e descrizione
1

public boolean compareAndSet(boolean expect, boolean update)

Atomicamente imposta il valore sul valore aggiornato dato se il valore corrente == il valore atteso.

2

public boolean get()

Restituisce il valore corrente.

3

public boolean getAndSet(boolean newValue)

Atomicamente imposta il valore dato e restituisce il valore precedente.

4

public void lazySet(boolean newValue)

Alla fine si imposta sul valore dato.

5

public void set(boolean newValue)

Imposta incondizionatamente sul valore dato.

6

public String toString()

Restituisce la rappresentazione String del valore corrente.

7

public boolean weakCompareAndSet(boolean expect, boolean update)

Atomicamente imposta il valore sul valore aggiornato dato se il valore corrente == il valore atteso.

Esempio

Il seguente programma TestThread mostra l'utilizzo della variabile AtomicBoolean in un ambiente basato su thread.

import java.util.concurrent.atomic.AtomicBoolean;

public class TestThread {

   public static void main(final String[] arguments) throws InterruptedException {
      final AtomicBoolean atomicBoolean = new AtomicBoolean(false);

      new Thread("Thread 1") {

         public void run() {

            while(true) {
               System.out.println(Thread.currentThread().getName() 
                  +" Waiting for Thread 2 to set Atomic variable to true. Current value is "
                  + atomicBoolean.get());

               if(atomicBoolean.compareAndSet(true, false)) {
                  System.out.println("Done!");
                  break;
               }
            }
         };
      }.start();

      new Thread("Thread 2") {

         public void run() {
            System.out.println(Thread.currentThread().getName() +
               ", Atomic Variable: " +atomicBoolean.get()); 
            System.out.println(Thread.currentThread().getName() +
               " is setting the variable to true ");
            atomicBoolean.set(true);
            System.out.println(Thread.currentThread().getName() +
               ", Atomic Variable: " +atomicBoolean.get()); 
         };
      }.start();
   }
}

Questo produrrà il seguente risultato.

Produzione

Thread 1 Waiting for Thread 2 to set Atomic variable to true. Current value is false
Thread 1 Waiting for Thread 2 to set Atomic variable to true. Current value is false
Thread 1 Waiting for Thread 2 to set Atomic variable to true. Current value is false
Thread 2, Atomic Variable: false
Thread 1 Waiting for Thread 2 to set Atomic variable to true. Current value is false
Thread 2 is setting the variable to true
Thread 2, Atomic Variable: true
Thread 1 Waiting for Thread 2 to set Atomic variable to true. Current value is false
Done!

Una classe java.util.concurrent.atomic.AtomicReference fornisce operazioni sul riferimento all'oggetto sottostante che possono essere lette e scritte in modo atomico e contiene anche operazioni atomiche avanzate. AtomicReference supporta operazioni atomiche sulla variabile di riferimento dell'oggetto sottostante. Ha metodi get e set che funzionano come letture e scritture su variabili volatili. Cioè, un insieme ha una relazione accade prima con qualsiasi successivo get sulla stessa variabile. Anche il metodo compareAndSet atomico ha queste caratteristiche di coerenza della memoria.

Metodi AtomicReference

Di seguito è riportato l'elenco dei metodi importanti disponibili nella classe AtomicReference.

Sr.No. Metodo e descrizione
1

public boolean compareAndSet(V expect, V update)

Atomicamente imposta il valore sul valore aggiornato dato se il valore corrente == il valore atteso.

2

public boolean get()

Restituisce il valore corrente.

3

public boolean getAndSet(V newValue)

Atomicamente imposta il valore dato e restituisce il valore precedente.

4

public void lazySet(V newValue)

Alla fine si imposta sul valore dato.

5

public void set(V newValue)

Imposta incondizionatamente sul valore dato.

6

public String toString()

Restituisce la rappresentazione String del valore corrente.

7

public boolean weakCompareAndSet(V expect, V update)

Atomicamente imposta il valore sul valore aggiornato dato se il valore corrente == il valore atteso.

Esempio

Il seguente programma TestThread mostra l'utilizzo della variabile AtomicReference in un ambiente basato su thread.

import java.util.concurrent.atomic.AtomicReference;

public class TestThread {
   private static String message = "hello";
   private static AtomicReference<String> atomicReference;

   public static void main(final String[] arguments) throws InterruptedException {
      atomicReference = new AtomicReference<String>(message);
      
      new Thread("Thread 1") {
         
         public void run() {
            atomicReference.compareAndSet(message, "Thread 1");
            message = message.concat("-Thread 1!");
         };
      }.start();

      System.out.println("Message is: " + message);
      System.out.println("Atomic Reference of Message is: " + atomicReference.get());
   }
}

Questo produrrà il seguente risultato.

Produzione

Message is: hello
Atomic Reference of Message is: Thread 1

Una classe java.util.concurrent.atomic.AtomicIntegerArray fornisce operazioni sull'array int sottostante che possono essere lette e scritte in modo atomico e contiene anche operazioni atomiche avanzate. AtomicIntegerArray supporta operazioni atomiche sulla variabile matrice int sottostante. Ha metodi get e set che funzionano come letture e scritture su variabili volatili. Cioè, un insieme ha una relazione accade prima con qualsiasi successivo get sulla stessa variabile. Anche il metodo compareAndSet atomico ha queste caratteristiche di coerenza della memoria.

Metodi AtomicIntegerArray

Di seguito è riportato l'elenco dei metodi importanti disponibili nella classe AtomicIntegerArray.

Sr.No. Metodo e descrizione
1

public int addAndGet(int i, int delta)

Atomicamente aggiunge il valore dato all'elemento all'indice i.

2

public boolean compareAndSet(int i, int expect, int update)

Atomicamente imposta l'elemento nella posizione i al valore aggiornato dato se il valore corrente == il valore atteso.

3

public int decrementAndGet(int i)

Atomicamente decrementa di uno l'elemento all'indice i.

4

public int get(int i)

Ottiene il valore corrente nella posizione i.

5

public int getAndAdd(int i, int delta)

Atomicamente aggiunge il valore dato all'elemento all'indice i.

6

public int getAndDecrement(int i)

Atomicamente decrementa di uno l'elemento all'indice i.

7

public int getAndIncrement(int i)

Atomicamente incrementa di uno l'elemento all'indice i.

8

public int getAndSet(int i, int newValue)

Atomicamente imposta l'elemento nella posizione i al valore dato e restituisce il vecchio valore.

9

public int incrementAndGet(int i)

Atomicamente incrementa di uno l'elemento all'indice i.

10

public void lazySet(int i, int newValue)

Alla fine imposta l'elemento in posizione i al valore dato.

11

public int length()

Restituisce la lunghezza della matrice.

12

public void set(int i, int newValue)

Imposta l'elemento in posizione i sul valore dato.

13

public String toString()

Restituisce la rappresentazione String dei valori correnti di array.

14

public boolean weakCompareAndSet(int i, int expect, int update)

Atomicamente imposta l'elemento nella posizione i al valore aggiornato dato se il valore corrente == il valore atteso.

Esempio

Il seguente programma TestThread mostra l'utilizzo della variabile AtomicIntegerArray in un ambiente basato su thread.

import java.util.concurrent.atomic.AtomicIntegerArray;

public class TestThread {
   private static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);

   public static void main(final String[] arguments) throws InterruptedException {
      
      for (int i = 0; i<atomicIntegerArray.length(); i++) {
         atomicIntegerArray.set(i, 1);
      }

      Thread t1 = new Thread(new Increment());
      Thread t2 = new Thread(new Compare());
      t1.start();
      t2.start();

      t1.join();
      t2.join();

      System.out.println("Values: ");

      for (int i = 0; i<atomicIntegerArray.length(); i++) {
         System.out.print(atomicIntegerArray.get(i) + " ");
      }
   }

   static class Increment implements Runnable {

      public void run() {

         for(int i = 0; i<atomicIntegerArray.length(); i++) {
            int add = atomicIntegerArray.incrementAndGet(i);
            System.out.println("Thread " + Thread.currentThread().getId() 
               + ", index " +i + ", value: "+ add);
         }
      }
   }

   static class Compare implements Runnable {

      public void run() {

         for(int i = 0; i<atomicIntegerArray.length(); i++) {
            boolean swapped = atomicIntegerArray.compareAndSet(i, 2, 3);
            
            if(swapped) {
               System.out.println("Thread " + Thread.currentThread().getId()
                  + ", index " +i + ", value: 3");
            }
         }
      }
   }
}

Questo produrrà il seguente risultato.

Produzione

Thread 10, index 0, value: 2
Thread 10, index 1, value: 2
Thread 10, index 2, value: 2
Thread 11, index 0, value: 3
Thread 10, index 3, value: 2
Thread 11, index 1, value: 3
Thread 11, index 2, value: 3
Thread 10, index 4, value: 2
Thread 11, index 3, value: 3
Thread 10, index 5, value: 2
Thread 10, index 6, value: 2
Thread 11, index 4, value: 3
Thread 10, index 7, value: 2
Thread 11, index 5, value: 3
Thread 10, index 8, value: 2
Thread 11, index 6, value: 3
Thread 10, index 9, value: 2
Thread 11, index 7, value: 3
Thread 11, index 8, value: 3
Thread 11, index 9, value: 3
Values:
3 3 3 3 3 3 3 3 3 3

Una classe java.util.concurrent.atomic.AtomicLongArray fornisce operazioni sul long array sottostante che possono essere lette e scritte atomicamente e contiene anche operazioni atomiche avanzate. AtomicLongArray supporta operazioni atomiche sulla variabile long array sottostante. Ha metodi get e set che funzionano come letture e scritture su variabili volatili. Cioè, un insieme ha una relazione accade prima con qualsiasi successivo get sulla stessa variabile. Anche il metodo compareAndSet atomico ha queste caratteristiche di coerenza della memoria.

Metodi AtomicLongArray

Di seguito è riportato l'elenco dei metodi importanti disponibili nella classe AtomicLongArray.

Sr.No. Metodo e descrizione
1

public long addAndGet(int i, long delta)

Atomicamente aggiunge il valore dato all'elemento all'indice i.

2

public boolean compareAndSet(int i, long expect, long update)

Atomicamente imposta l'elemento nella posizione i al valore aggiornato dato se il valore corrente == il valore atteso.

3

public long decrementAndGet(int i)

Atomicamente decrementa di uno l'elemento all'indice i.

4

public long get(int i)

Ottiene il valore corrente nella posizione i.

5

public long getAndAdd(int i, long delta)

Atomicamente aggiunge il valore dato all'elemento all'indice i.

6

public long getAndDecrement(int i)

Atomicamente decrementa di uno l'elemento all'indice i.

7

public long getAndIncrement(int i)

Atomicamente incrementa di uno l'elemento all'indice i.

8

public long getAndSet(int i, long newValue)

Atomicamente imposta l'elemento nella posizione i al valore dato e restituisce il vecchio valore.

9

public long incrementAndGet(int i)

Atomicamente incrementa di uno l'elemento all'indice i.

10

public void lazySet(int i, long newValue)

Alla fine imposta l'elemento in posizione i al valore dato.

11

public int length()

Restituisce la lunghezza della matrice.

12

public void set(int i, long newValue)

Imposta l'elemento in posizione i sul valore dato.

13

public String toString()

Restituisce la rappresentazione String dei valori correnti di array.

14

public boolean weakCompareAndSet(int i, long expect, long update)

Atomicamente imposta l'elemento nella posizione i al valore aggiornato dato se il valore corrente == il valore atteso.

Esempio

Il seguente programma TestThread mostra l'utilizzo della variabile AtomicIntegerArray in un ambiente basato su thread.

import java.util.concurrent.atomic.AtomicLongArray;

public class TestThread {
   private static AtomicLongArray atomicLongArray = new AtomicLongArray(10);

   public static void main(final String[] arguments) throws InterruptedException {

      for (int i = 0; i<atomicLongArray.length(); i++) {
         atomicLongArray.set(i, 1);
      }

      Thread t1 = new Thread(new Increment());
      Thread t2 = new Thread(new Compare());
      t1.start();
      t2.start();

      t1.join();
      t2.join();

      System.out.println("Values: ");
      
      for (int i = 0; i<atomicLongArray.length(); i++) {
         System.out.print(atomicLongArray.get(i) + " ");
      }
   }  

   static class Increment implements Runnable {

      public void run() {

         for(int i = 0; i<atomicLongArray.length(); i++) {
            long add = atomicLongArray.incrementAndGet(i);
            System.out.println("Thread " + Thread.currentThread().getId() 
               + ", index " +i + ", value: "+ add);
         }
      }
   }

   static class Compare implements Runnable {

      public void run() {

         for(int i = 0; i<atomicLongArray.length(); i++) {
            boolean swapped = atomicLongArray.compareAndSet(i, 2, 3);
            
            if(swapped) {
               System.out.println("Thread " + Thread.currentThread().getId()
                  + ", index " +i + ", value: 3");
            }
         }
      }
   }
}

Questo produrrà il seguente risultato.

Produzione

Thread 9, index 0, value: 2
Thread 10, index 0, value: 3
Thread 9, index 1, value: 2
Thread 9, index 2, value: 2
Thread 9, index 3, value: 2
Thread 9, index 4, value: 2
Thread 10, index 1, value: 3
Thread 9, index 5, value: 2
Thread 10, index 2, value: 3
Thread 9, index 6, value: 2
Thread 10, index 3, value: 3
Thread 9, index 7, value: 2
Thread 10, index 4, value: 3
Thread 9, index 8, value: 2
Thread 9, index 9, value: 2
Thread 10, index 5, value: 3
Thread 10, index 6, value: 3
Thread 10, index 7, value: 3
Thread 10, index 8, value: 3
Thread 10, index 9, value: 3
Values: 
3 3 3 3 3 3 3 3 3 3

Una classe java.util.concurrent.atomic.AtomicReferenceArray fornisce operazioni sull'array di riferimento sottostante che possono essere lette e scritte atomicamente e contiene anche operazioni atomiche avanzate. AtomicReferenceArray supporta operazioni atomiche sulla variabile della matrice di riferimento sottostante. Ha metodi get e set che funzionano come letture e scritture su variabili volatili. Cioè, un insieme ha una relazione accade prima con qualsiasi successivo get sulla stessa variabile. Anche il metodo compareAndSet atomico ha queste caratteristiche di coerenza della memoria.

Metodi AtomicReferenceArray

Di seguito è riportato l'elenco dei metodi importanti disponibili nella classe AtomicReferenceArray.

Sr.No. Metodo e descrizione
1

public boolean compareAndSet(int i, E expect, E update)

Atomicamente imposta l'elemento nella posizione i al valore aggiornato dato se il valore corrente == il valore atteso.

2

public E get(int i)

Ottiene il valore corrente nella posizione i.

3

public E getAndSet(int i, E newValue)

Atomicamente imposta l'elemento nella posizione i al valore dato e restituisce il vecchio valore.

4

public void lazySet(int i, E newValue)

Alla fine imposta l'elemento in posizione i al valore dato.

5

public int length()

Restituisce la lunghezza della matrice.

6

public void set(int i, E newValue)

Imposta l'elemento in posizione i sul valore dato.

7

public String toString()

Restituisce la rappresentazione String dei valori correnti di array.

8

public boolean weakCompareAndSet(int i, E expect, E update)

Atomicamente imposta l'elemento nella posizione i al valore aggiornato dato se il valore corrente == il valore atteso.

Esempio

Il seguente programma TestThread mostra l'utilizzo della variabile AtomicReferenceArray in un ambiente basato su thread.

import java.util.concurrent.atomic.AtomicReferenceArray;

public class TestThread {
   private static String[] source = new String[10];
   private static AtomicReferenceArray<String> atomicReferenceArray 
      = new AtomicReferenceArray<String>(source);

   public static void main(final String[] arguments) throws InterruptedException {

      for (int i = 0; i<atomicReferenceArray.length(); i++) {
         atomicReferenceArray.set(i, "item-2");
      }

      Thread t1 = new Thread(new Increment());
      Thread t2 = new Thread(new Compare());
      t1.start();
      t2.start();

      t1.join();
      t2.join();		
   }  

   static class Increment implements Runnable {
      
      public void run() {
         
         for(int i = 0; i<atomicReferenceArray.length(); i++) {
            String add = atomicReferenceArray.getAndSet(i,"item-"+ (i+1));
            System.out.println("Thread " + Thread.currentThread().getId() 
               + ", index " +i + ", value: "+ add);
         }
      }
   }

   static class Compare implements Runnable {
      
      public void run() {
         
         for(int i = 0; i<atomicReferenceArray.length(); i++) {
            System.out.println("Thread " + Thread.currentThread().getId() 
               + ", index " +i + ", value: "+ atomicReferenceArray.get(i));
            boolean swapped = atomicReferenceArray.compareAndSet(i, "item-2", "updated-item-2");
            System.out.println("Item swapped: " + swapped);
            
            if(swapped) {
               System.out.println("Thread " + Thread.currentThread().getId() 
                  + ", index " +i + ", updated-item-2");
            }
         }
      }
   }
}

Questo produrrà il seguente risultato.

Produzione

Thread 9, index 0, value: item-2
Thread 10, index 0, value: item-1
Item swapped: false
Thread 10, index 1, value: item-2
Item swapped: true
Thread 9, index 1, value: updated-item-2
Thread 10, index 1, updated-item-2
Thread 10, index 2, value: item-3
Item swapped: false
Thread 10, index 3, value: item-2
Item swapped: true
Thread 10, index 3, updated-item-2
Thread 10, index 4, value: item-2
Item swapped: true
Thread 10, index 4, updated-item-2
Thread 10, index 5, value: item-2
Item swapped: true
Thread 10, index 5, updated-item-2
Thread 10, index 6, value: item-2
Thread 9, index 2, value: item-2
Item swapped: true
Thread 9, index 3, value: updated-item-2
Thread 10, index 6, updated-item-2
Thread 10, index 7, value: item-2
Thread 9, index 4, value: updated-item-2
Item swapped: true
Thread 9, index 5, value: updated-item-2
Thread 10, index 7, updated-item-2
Thread 9, index 6, value: updated-item-2
Thread 10, index 8, value: item-2
Thread 9, index 7, value: updated-item-2
Item swapped: true
Thread 9, index 8, value: updated-item-2
Thread 10, index 8, updated-item-2
Thread 9, index 9, value: item-2
Thread 10, index 9, value: item-10
Item swapped: false

Un'interfaccia java.util.concurrent.Executor è una semplice interfaccia per supportare l'avvio di nuove attività.

Metodi ExecutorService

Sr.No. Metodo e descrizione
1

void execute(Runnable command)

Esegue il comando dato in un momento futuro.

Esempio

Il seguente programma TestThread mostra l'utilizzo dell'interfaccia Executor in un ambiente basato su thread.

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TestThread {

   public static void main(final String[] arguments) throws InterruptedException {
      Executor executor = Executors.newCachedThreadPool();
      executor.execute(new Task());
      ThreadPoolExecutor pool = (ThreadPoolExecutor)executor;
      pool.shutdown();
   }  

   static class Task implements Runnable {
      
      public void run() {
         
         try {
            Long duration = (long) (Math.random() * 5);
            System.out.println("Running Task!");
            TimeUnit.SECONDS.sleep(duration);
            System.out.println("Task Completed");
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }
}

Questo produrrà il seguente risultato.

Produzione

Running Task!
Task Completed

Un'interfaccia java.util.concurrent.ExecutorService è una sottointerfaccia dell'interfaccia Executor e aggiunge funzionalità per gestire il ciclo di vita, sia delle singole attività che dell'esecutore stesso.

Metodi ExecutorService

Sr.No. Metodo e descrizione
1

boolean awaitTermination(long timeout, TimeUnit unit)

Si blocca fino a quando tutte le attività non hanno completato l'esecuzione dopo una richiesta di arresto, o si verifica il timeout o il thread corrente viene interrotto, a seconda di cosa si verifica per prima.

2

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)

Esegue le attività date, restituendo un elenco di Futures che mantengono il loro stato e i risultati quando tutti sono stati completati.

3

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)

Esegue le attività date, restituendo un elenco di Futures che mantengono il loro stato e i risultati quando tutto è completato o il timeout scade, a seconda di cosa si verifica per prima.

4

<T> T invokeAny(Collection<? extends Callable<T>> tasks)

Esegue i compiti dati, restituendo il risultato di uno che è stato completato con successo (cioè, senza lanciare un'eccezione), se ce ne sono.

5

<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)

Esegue le attività date, restituendo il risultato di una che è stata completata con successo (cioè, senza lanciare un'eccezione), se ce ne sono prima che sia trascorso il timeout specificato.
6

boolean isShutdown()

Restituisce vero se questo esecutore è stato chiuso.

7

boolean isTerminated()

Restituisce vero se tutte le attività sono state completate dopo l'arresto.

8

void shutdown()

Avvia un arresto ordinato in cui vengono eseguite le attività inoltrate in precedenza, ma non verranno accettate nuove attività.

9

List<Runnable> shutdownNow()

Tenta di interrompere tutte le attività in esecuzione attiva, interrompe l'elaborazione delle attività in attesa e restituisce un elenco delle attività in attesa di esecuzione.

10

<T> Future<T> submit(Callable<T> task)

Invia un'attività di restituzione del valore per l'esecuzione e restituisce un Future che rappresenta i risultati in sospeso dell'attività.

11

Future<?> submit(Runnable task)

Invia un'attività eseguibile per l'esecuzione e restituisce un Future che rappresenta tale attività.

12

<T> Future<T> submit(Runnable task, T result)

Invia un'attività eseguibile per l'esecuzione e restituisce un Future che rappresenta tale attività.

Esempio

Il seguente programma TestThread mostra l'utilizzo dell'interfaccia ExecutorService in un ambiente basato su thread.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class TestThread {

   public static void main(final String[] arguments) throws InterruptedException {
      ExecutorService executor = Executors.newSingleThreadExecutor();

      try {
         executor.submit(new Task());
         System.out.println("Shutdown executor");
         executor.shutdown();
         executor.awaitTermination(5, TimeUnit.SECONDS);
      } catch (InterruptedException e) {
         System.err.println("tasks interrupted");
      } finally {

         if (!executor.isTerminated()) {
            System.err.println("cancel non-finished tasks");
         }
         executor.shutdownNow();
         System.out.println("shutdown finished");
      }
   }

   static class Task implements Runnable {
      
      public void run() {
         
         try {
            Long duration = (long) (Math.random() * 20);
            System.out.println("Running Task!");
            TimeUnit.SECONDS.sleep(duration);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }	   
}

Questo produrrà il seguente risultato.

Produzione

Shutdown executor
Running Task!
shutdown finished
cancel non-finished tasks
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:302)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:328)
	at TestThread$Task.run(TestThread.java:39)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:439) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
	at java.util.concurrent.FutureTask.run(FutureTask.java:138)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
	at java.lang.Thread.run(Thread.java:662)

Un'interfaccia java.util.concurrent.ScheduledExecutorService è una sottointerfaccia dell'interfaccia ExecutorService e supporta l'esecuzione futura e / o periodica delle attività.

Metodi ScheduledExecutorService

Sr.No. Metodo e descrizione
1

<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)

Crea ed esegue un ScheduledFuture che viene abilitato dopo il ritardo specificato.

2

ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)

Crea ed esegue un'azione one-shot che viene abilitata dopo il ritardo specificato.

3

ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

Crea ed esegue un'azione periodica che viene abilitata prima dopo il dato ritardo iniziale e successivamente con il dato periodo; ovvero le esecuzioni inizieranno dopo initialDelay, quindi initialDelay + period, quindi initialDelay + 2 * period e così via.

4

ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)

Crea ed esegue un'azione periodica che viene abilitata prima dopo il dato ritardo iniziale e successivamente con il dato ritardo tra la fine di un'esecuzione e l'inizio della successiva.

Esempio

Il seguente programma TestThread mostra l'utilizzo dell'interfaccia ScheduledExecutorService in un ambiente basato su thread.

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

public class TestThread {

   public static void main(final String[] arguments) throws InterruptedException {
      final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

      final ScheduledFuture<?> beepHandler = 
         scheduler.scheduleAtFixedRate(new BeepTask(), 2, 2, TimeUnit.SECONDS);

      scheduler.schedule(new Runnable() {

         @Override
         public void run() {
            beepHandler.cancel(true);
            scheduler.shutdown();			
         }
      }, 10, TimeUnit.SECONDS);
   }

   static class BeepTask implements Runnable {
      
      public void run() {
         System.out.println("beep");      
      }
   }
}

Questo produrrà il seguente risultato.

Produzione

beep
beep
beep
beep

Un pool di thread fisso può essere ottenuto chiamando il metodo statico newFixedThreadPool () della classe Executors.

Sintassi

ExecutorService fixedPool = Executors.newFixedThreadPool(2);

dove

  • Saranno attivi massimo 2 thread per elaborare le attività.

  • Se vengono inviati più di 2 thread, vengono tenuti in coda fino a quando i thread non diventano disponibili.

  • Viene creato un nuovo thread per sostituirlo se un thread termina a causa di un errore durante l'esecuzione, l'arresto dell'esecutore non è ancora stato chiamato.

  • Qualsiasi thread esiste fino all'arresto del pool.

Esempio

Il seguente programma TestThread mostra l'utilizzo del metodo newFixedThreadPool in un ambiente basato su thread.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TestThread {
	
   public static void main(final String[] arguments) throws InterruptedException {
      ExecutorService executor = Executors.newFixedThreadPool(2);

      // Cast the object to its class type
      ThreadPoolExecutor pool = (ThreadPoolExecutor) executor;

      //Stats before tasks execution
      System.out.println("Largest executions: "
         + pool.getLargestPoolSize());
      System.out.println("Maximum allowed threads: "
         + pool.getMaximumPoolSize());
      System.out.println("Current threads in pool: "
         + pool.getPoolSize());
      System.out.println("Currently executing threads: "
         + pool.getActiveCount());
      System.out.println("Total number of threads(ever scheduled): "
         + pool.getTaskCount());

      executor.submit(new Task());
      executor.submit(new Task());

      //Stats after tasks execution
      System.out.println("Core threads: " + pool.getCorePoolSize());
      System.out.println("Largest executions: "
         + pool.getLargestPoolSize());
      System.out.println("Maximum allowed threads: "
         + pool.getMaximumPoolSize());
      System.out.println("Current threads in pool: "
         + pool.getPoolSize());
      System.out.println("Currently executing threads: "
         + pool.getActiveCount());
      System.out.println("Total number of threads(ever scheduled): "
         + pool.getTaskCount());

      executor.shutdown();
   }  

   static class Task implements Runnable {

      public void run() {
         
         try {
            Long duration = (long) (Math.random() * 5);
            System.out.println("Running Task! Thread Name: " +
               Thread.currentThread().getName());
               TimeUnit.SECONDS.sleep(duration);
            
            System.out.println("Task Completed! Thread Name: " +
               Thread.currentThread().getName());
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }
}

Questo produrrà il seguente risultato.

Produzione

Largest executions: 0
Maximum allowed threads: 2
Current threads in pool: 0
Currently executing threads: 0
Total number of threads(ever scheduled): 0
Core threads: 2
Largest executions: 2
Maximum allowed threads: 2
Current threads in pool: 2
Currently executing threads: 1
Total number of threads(ever scheduled): 2
Running Task! Thread Name: pool-1-thread-1
Running Task! Thread Name: pool-1-thread-2
Task Completed! Thread Name: pool-1-thread-2
Task Completed! Thread Name: pool-1-thread-1

Un pool di thread memorizzato nella cache può essere ottenuto chiamando il metodo statico newCachedThreadPool () della classe Executors.

Sintassi

ExecutorService executor = Executors.newCachedThreadPool();

dove

  • Il metodo newCachedThreadPool crea un esecutore con un pool di thread espandibile.

  • Un tale esecutore è adatto per applicazioni che avviano molte attività di breve durata.

Esempio

Il seguente programma TestThread mostra l'utilizzo del metodo newCachedThreadPool in un ambiente basato su thread.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TestThread {
	
   public static void main(final String[] arguments) throws InterruptedException {
      ExecutorService executor = Executors.newCachedThreadPool();

      // Cast the object to its class type
      ThreadPoolExecutor pool = (ThreadPoolExecutor) executor;

      //Stats before tasks execution
      System.out.println("Largest executions: "
         + pool.getLargestPoolSize());
      System.out.println("Maximum allowed threads: "
         + pool.getMaximumPoolSize());
      System.out.println("Current threads in pool: "
         + pool.getPoolSize());
      System.out.println("Currently executing threads: "
         + pool.getActiveCount());
      System.out.println("Total number of threads(ever scheduled): "
         + pool.getTaskCount());

      executor.submit(new Task());
      executor.submit(new Task());

      //Stats after tasks execution
      System.out.println("Core threads: " + pool.getCorePoolSize());
      System.out.println("Largest executions: "
         + pool.getLargestPoolSize());
      System.out.println("Maximum allowed threads: "
         + pool.getMaximumPoolSize());
      System.out.println("Current threads in pool: "
         + pool.getPoolSize());
      System.out.println("Currently executing threads: "
         + pool.getActiveCount());
      System.out.println("Total number of threads(ever scheduled): "
         + pool.getTaskCount());

      executor.shutdown();
   }  

   static class Task implements Runnable {

      public void run() {
         
         try {
            Long duration = (long) (Math.random() * 5);
            System.out.println("Running Task! Thread Name: " +
               Thread.currentThread().getName());
               TimeUnit.SECONDS.sleep(duration);
            System.out.println("Task Completed! Thread Name: " +
               Thread.currentThread().getName());
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }
}

Questo produrrà il seguente risultato.

Produzione

Largest executions: 0
Maximum allowed threads: 2147483647
Current threads in pool: 0
Currently executing threads: 0
Total number of threads(ever scheduled): 0
Core threads: 0
Largest executions: 2
Maximum allowed threads: 2147483647
Current threads in pool: 2
Currently executing threads: 2
Total number of threads(ever scheduled): 2
Running Task! Thread Name: pool-1-thread-1
Running Task! Thread Name: pool-1-thread-2
Task Completed! Thread Name: pool-1-thread-2
Task Completed! Thread Name: pool-1-thread-1

Un pool di thread pianificato può essere ottenuto chiamando il metodo statico newScheduledThreadPool () della classe Executors.

Sintassi

ExecutorService executor = Executors.newScheduledThreadPool(1);

Esempio

Il seguente programma TestThread mostra l'utilizzo del metodo newScheduledThreadPool in un ambiente basato su thread.

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

public class TestThread {

   public static void main(final String[] arguments) throws InterruptedException {
      final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

      final ScheduledFuture<?> beepHandler = 
         scheduler.scheduleAtFixedRate(new BeepTask(), 2, 2, TimeUnit.SECONDS);

      scheduler.schedule(new Runnable() {

         @Override
         public void run() {
            beepHandler.cancel(true);
            scheduler.shutdown();			
         }
      }, 10, TimeUnit.SECONDS);
   }  

   static class BeepTask implements Runnable {

      public void run() {
         System.out.println("beep");      
      }
   }
}

Questo produrrà il seguente risultato.

Produzione

beep
beep
beep
beep

È possibile ottenere un singolo pool di thread chiamando il metodo statico newSingleThreadExecutor () della classe Executors.

Sintassi

ExecutorService executor = Executors.newSingleThreadExecutor();

Dove il metodo newSingleThreadExecutor crea un esecutore che esegue una singola attività alla volta.

Esempio

Il seguente programma TestThread mostra l'utilizzo del metodo newSingleThreadExecutor in un ambiente basato su thread.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class TestThread {

   public static void main(final String[] arguments) throws InterruptedException {
      ExecutorService executor = Executors.newSingleThreadExecutor();

      try {
         executor.submit(new Task());
         System.out.println("Shutdown executor");
         executor.shutdown();
         executor.awaitTermination(5, TimeUnit.SECONDS);
      } catch (InterruptedException e) {
         System.err.println("tasks interrupted");
      } finally {

         if (!executor.isTerminated()) {
            System.err.println("cancel non-finished tasks");
         }
         executor.shutdownNow();
         System.out.println("shutdown finished");
      }
   }

   static class Task implements Runnable {
      
      public void run() {

         try {
            Long duration = (long) (Math.random() * 20);
            System.out.println("Running Task!");
            TimeUnit.SECONDS.sleep(duration);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }
}

Questo produrrà il seguente risultato.

Produzione

Shutdown executor
Running Task!
shutdown finished
cancel non-finished tasks
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:302)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:328)
	at TestThread$Task.run(TestThread.java:39) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:439)
	at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303) at java.util.concurrent.FutureTask.run(FutureTask.java:138) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
	at java.lang.Thread.run(Thread.java:662)

java.util.concurrent.ThreadPoolExecutor è un ExecutorService per eseguire ciascuna attività inoltrata utilizzando uno dei possibili diversi thread in pool, normalmente configurati utilizzando i metodi di fabbrica di Executors. Fornisce inoltre vari metodi di utilità per controllare le statistiche dei thread correnti e controllarli.

Metodi ThreadPoolExecutor

Sr.No. Metodo e descrizione
1

protected void afterExecute(Runnable r, Throwable t)

Metodo invocato al completamento dell'esecuzione del Runnable specificato.

2

void allowCoreThreadTimeOut(boolean value)

Imposta la politica che stabilisce se i thread principali possono scadere e terminare se non arrivano attività entro il tempo di mantenimento, sostituendo se necessario quando arrivano nuove attività.

3

boolean allowsCoreThreadTimeOut()

Restituisce vero se questo pool consente ai thread principali di scadere e terminare se non arrivano attività entro il tempo keepAlive, essendo sostituito se necessario quando arrivano nuove attività.

4

boolean awaitTermination(long timeout, TimeUnit unit)

Si blocca fino a quando tutte le attività non hanno completato l'esecuzione dopo una richiesta di arresto, o si verifica il timeout o il thread corrente viene interrotto, a seconda di cosa si verifica per prima.

5

protected void beforeExecute(Thread t, Runnable r)

Metodo richiamato prima di eseguire il Runnable specificato nel thread specificato.

6

void execute(Runnable command)

Esegue l'attività data in futuro.

7

protected void finalize()

Richiama l'arresto quando questo esecutore non è più referenziato e non ha thread.

8

int getActiveCount()

Restituisce il numero approssimativo di thread che stanno attivamente eseguendo attività.

9

long getCompletedTaskCount()

Restituisce il numero totale approssimativo di attività che hanno completato l'esecuzione.

10

int getCorePoolSize()

Restituisce il numero principale di thread.

11

long getKeepAliveTime(TimeUnit unit)

Restituisce il tempo di mantenimento del thread, ovvero la quantità di tempo in cui i thread in eccesso rispetto alla dimensione del pool di base possono rimanere inattivi prima di essere terminati.

12

int getLargestPoolSize()

Restituisce il maggior numero di thread che siano mai stati contemporaneamente nel pool.

13

int getMaximumPoolSize()

Restituisce il numero massimo consentito di thread.

14

int getPoolSize()

Restituisce il numero corrente di thread nel pool.

15

BlockingQueue getQueue()

Restituisce la coda delle attività utilizzata da questo esecutore.

15

RejectedExecutionHandler getRejectedExecutionHandler()

Restituisce il gestore corrente per le attività non eseguibili.

16

long getTaskCount()

Restituisce il numero totale approssimativo di attività che sono state pianificate per l'esecuzione.

17

ThreadFactory getThreadFactory()

Restituisce la produzione di thread utilizzata per creare nuovi thread.

18

boolean isShutdown()

Restituisce vero se questo esecutore è stato chiuso.

19

boolean isTerminated()

Restituisce vero se tutte le attività sono state completate dopo l'arresto.

20

boolean isTerminating()

Restituisce vero se questo esecutore sta per terminare dopo shutdown () o shutdownNow () ma non è stato completamente terminato.

21

int prestartAllCoreThreads()

Avvia tutti i thread principali, facendoli attendere pigramente il lavoro.

22

boolean prestartCoreThread()

Avvia un thread principale, facendolo aspettare pigramente il lavoro.

23

void purge()

Tenta di rimuovere dalla coda di lavoro tutte le attività future che sono state annullate.

24

boolean remove(Runnable task)

Rimuove questa attività dalla coda interna dell'esecutore se presente, impedendone l'esecuzione se non è già stata avviata.

25

void setCorePoolSize(int corePoolSize)

Imposta il numero principale di thread.

26

void setKeepAliveTime(long time, TimeUnit unit)

Imposta il limite di tempo per il quale i thread possono rimanere inattivi prima di essere terminati.

27

void setMaximumPoolSize(int maximumPoolSize)

Imposta il numero massimo consentito di thread.

28

void setRejectedExecutionHandler(RejectedExecutionHandler handler)

Imposta un nuovo gestore per le attività non eseguibili.

29

void setThreadFactory(ThreadFactory threadFactory)

Imposta la fabbrica di thread utilizzata per creare nuovi thread.

30

void shutdown()

Avvia un arresto ordinato in cui vengono eseguite le attività inoltrate in precedenza, ma non verranno accettate nuove attività.

31

List<Runnable> shutdownNow()

Tenta di interrompere tutte le attività in esecuzione attiva, interrompe l'elaborazione delle attività in attesa e restituisce un elenco delle attività in attesa di esecuzione.

32

protected void terminated()

Metodo richiamato quando l'Executor è terminato.

33

String toString()

Restituisce una stringa che identifica questo pool, nonché il suo stato, comprese le indicazioni dello stato di esecuzione e dei conteggi stimati di lavoratori e attività.

Esempio

Il seguente programma TestThread mostra l'utilizzo dell'interfaccia ThreadPoolExecutor in un ambiente basato su thread.

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TestThread {
	
   public static void main(final String[] arguments) throws InterruptedException {
      ThreadPoolExecutor executor = (ThreadPoolExecutor)Executors.newCachedThreadPool();

      //Stats before tasks execution
      System.out.println("Largest executions: "
         + executor.getLargestPoolSize());
      System.out.println("Maximum allowed threads: "
         + executor.getMaximumPoolSize());
      System.out.println("Current threads in pool: "
         + executor.getPoolSize());
      System.out.println("Currently executing threads: "
         + executor.getActiveCount());
      System.out.println("Total number of threads(ever scheduled): "
         + executor.getTaskCount());

      executor.submit(new Task());
      executor.submit(new Task());

      //Stats after tasks execution
      System.out.println("Core threads: " + executor.getCorePoolSize());
      System.out.println("Largest executions: "
         + executor.getLargestPoolSize());
      System.out.println("Maximum allowed threads: "
         + executor.getMaximumPoolSize());
      System.out.println("Current threads in pool: "
         + executor.getPoolSize());
      System.out.println("Currently executing threads: "
         + executor.getActiveCount());
      System.out.println("Total number of threads(ever scheduled): "
         + executor.getTaskCount());

      executor.shutdown();
   }  

   static class Task implements Runnable {

      public void run() {

         try {
            Long duration = (long) (Math.random() * 5);
            System.out.println("Running Task! Thread Name: " +
               Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(duration);
            System.out.println("Task Completed! Thread Name: " +
               Thread.currentThread().getName());
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }
}

Questo produrrà il seguente risultato.

Produzione

Largest executions: 0
Maximum allowed threads: 2147483647
Current threads in pool: 0
Currently executing threads: 0
Total number of threads(ever scheduled): 0
Core threads: 0
Largest executions: 2
Maximum allowed threads: 2147483647
Current threads in pool: 2
Currently executing threads: 2
Total number of threads(ever scheduled): 2
Running Task! Thread Name: pool-1-thread-2
Running Task! Thread Name: pool-1-thread-1
Task Completed! Thread Name: pool-1-thread-1
Task Completed! Thread Name: pool-1-thread-2

java.util.concurrent.ScheduledThreadPoolExecutor è una sottoclasse di ThreadPoolExecutor e può inoltre pianificare l'esecuzione di comandi dopo un determinato ritardo o l'esecuzione periodica.

Metodi ScheduledThreadPoolExecutor

Sr.No. Metodo e descrizione
1

protected <V> RunnableScheduledFuture<V> decorateTask(Callable<V> callable, RunnableScheduledFuture<V> task)

Modifica o sostituisce l'attività utilizzata per eseguire un richiamabile.

2

protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task)

Modifica o sostituisce l'attività utilizzata per eseguire un eseguibile.

3

void execute(Runnable command)

Esegue il comando con zero ritardo richiesto.

4

boolean getContinueExistingPeriodicTasksAfterShutdownPolicy()

Ottiene il criterio se continuare a eseguire attività periodiche esistenti anche quando questo esecutore è stato arrestato.

5

boolean getExecuteExistingDelayedTasksAfterShutdownPolicy()

Ottiene il criterio sull'esecuzione delle attività ritardate esistenti anche quando questo esecutore è stato arrestato.

6

BlockingQueue<Runnable> getQueue()

Restituisce la coda delle attività utilizzata da questo esecutore.

7

boolean getRemoveOnCancelPolicy()

Ottiene il criterio se le attività annullate devono essere rimosse immediatamente dalla coda di lavoro al momento dell'annullamento.

8

<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)

Crea ed esegue un ScheduledFuture che viene abilitato dopo il ritardo specificato.

9

ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)

Crea ed esegue un'azione one-shot che viene abilitata dopo il ritardo specificato.

10

ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

Crea ed esegue un'azione periodica che viene abilitata prima dopo il dato ritardo iniziale e successivamente con il dato periodo; ovvero le esecuzioni inizieranno dopo initialDelay, quindi initialDelay + period, quindi initialDelay + 2 * period e così via.

11

ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)

Crea ed esegue un'azione periodica che viene abilitata prima dopo il dato ritardo iniziale e successivamente con il dato ritardo tra la fine di un'esecuzione e l'inizio della successiva.

12

void setContinueExistingPeriodicTasksAfterShutdownPolicy (boolean value)

Imposta il criterio se continuare a eseguire attività periodiche esistenti anche quando questo esecutore è stato chiuso.

13

void setExecuteExistingDelayedTasksAfterShutdownPolicy (boolean value)

Imposta la politica sull'esecuzione di attività ritardate esistenti anche quando questo esecutore è stato arrestato.

14

void setRemoveOnCancelPolicy(boolean value)

Imposta il criterio per stabilire se le attività annullate devono essere rimosse immediatamente dalla coda di lavoro al momento dell'annullamento.

15

void shutdown()

Avvia un arresto ordinato in cui vengono eseguite le attività inoltrate in precedenza, ma non verranno accettate nuove attività.

16

List<Runnable> shutdownNow()

Tenta di interrompere tutte le attività in esecuzione attiva, interrompe l'elaborazione delle attività in attesa e restituisce un elenco delle attività in attesa di esecuzione.

17

<T> Future<T> submit(Callable<T> task)

Invia un'attività di restituzione del valore per l'esecuzione e restituisce un Future che rappresenta i risultati in sospeso dell'attività.

18

Future<?> submit(Runnable task)

Invia un'attività eseguibile per l'esecuzione e restituisce un Future che rappresenta tale attività.

19

<T> Future<T> submit(Runnable task, T result)

Invia un'attività eseguibile per l'esecuzione e restituisce un Future che rappresenta tale attività.

Esempio

Il seguente programma TestThread mostra l'utilizzo dell'interfaccia ScheduledThreadPoolExecutor in un ambiente basato su thread.

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

public class TestThread {

   public static void main(final String[] arguments) throws InterruptedException {
      final ScheduledThreadPoolExecutor scheduler = 
         (ScheduledThreadPoolExecutor)Executors.newScheduledThreadPool(1);

      final ScheduledFuture<?> beepHandler = 
         scheduler.scheduleAtFixedRate(new BeepTask(), 2, 2, TimeUnit.SECONDS);

      scheduler.schedule(new Runnable() {

         @Override
         public void run() {
            beepHandler.cancel(true);
            scheduler.shutdown();			
         }
      }, 10, TimeUnit.SECONDS);
   }  

   static class BeepTask implements Runnable {
      
      public void run() {
         System.out.println("beep");      
      }
   }
}

Questo produrrà il seguente risultato.

Produzione

beep
beep
beep
beep

L'oggetto java.util.concurrent.Callable può restituire il risultato calcolato da un thread in contrasto con l'interfaccia eseguibile che può solo eseguire il thread. L'oggetto Callable restituisce l'oggetto Future che fornisce metodi per monitorare lo stato di avanzamento di un'attività eseguita da un thread. L'oggetto Future può essere utilizzato per controllare lo stato di un Callable e quindi recuperare il risultato dal Callable una volta terminato il thread. Fornisce inoltre funzionalità di timeout.

Sintassi

//submit the callable using ThreadExecutor
//and get the result as a Future object
Future<Long> result10 = executor.submit(new FactorialService(10));
 
//get the result using get method of the Future object
//get method waits till the thread execution and then return the result of the execution.
Long factorial10 = result10.get();

Esempio

Il seguente programma TestThread mostra l'utilizzo di Futures e Callables in un ambiente basato su thread.

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestThread {

   public static void main(final String[] arguments) throws InterruptedException,
      ExecutionException {

      ExecutorService executor = Executors.newSingleThreadExecutor();

      System.out.println("Factorial Service called for 10!");
      Future<Long> result10 = executor.submit(new FactorialService(10));

      System.out.println("Factorial Service called for 20!");
      Future<Long> result20 = executor.submit(new FactorialService(20));

      Long factorial10 = result10.get();
      System.out.println("10! = " + factorial10);

      Long factorial20 = result20.get();
      System.out.println("20! = " + factorial20);

      executor.shutdown();
   }  

   static class FactorialService implements Callable<Long> {
      private int number;

      public FactorialService(int number) {
         this.number = number;
      }

      @Override
      public Long call() throws Exception {
         return factorial();
      }

      private Long factorial() throws InterruptedException {
         long result = 1; 
         
         while (number != 0) { 
            result = number * result; 
            number--; 
            Thread.sleep(100); 
         }
         return result;	
      }
   }
}

Questo produrrà il seguente risultato.

Produzione

Factorial Service called for 10!
Factorial Service called for 20!
10! = 3628800
20! = 2432902008176640000

Il framework fork-join consente di interrompere una determinata attività su più worker e quindi attendere il risultato per combinarli. Sfrutta in larga misura la capacità della macchina multiprocessore. Di seguito sono riportati i concetti e gli oggetti principali utilizzati nel framework fork-join.

Forchetta

Fork è un processo in cui un'attività si divide in sotto-attività più piccole e indipendenti che possono essere eseguite contemporaneamente.

Sintassi

Sum left  = new Sum(array, low, mid);
left.fork();

Qui Sum è una sottoclasse di RecursiveTask e left.fork () suddivide l'attività in sotto-attività.

Aderire

Join è un processo in cui un'attività unisce tutti i risultati delle attività secondarie una volta terminata l'esecuzione delle attività secondarie, altrimenti continua ad attendere.

Sintassi

left.join();

Qui a sinistra c'è un oggetto della classe Sum.

ForkJoinPool

è uno speciale pool di thread progettato per funzionare con la suddivisione delle attività fork-and-join.

Sintassi

ForkJoinPool forkJoinPool = new ForkJoinPool(4);

Ecco un nuovo ForkJoinPool con un livello di parallelismo di 4 CPU.

RecursiveAction

RecursiveAction rappresenta un'attività che non restituisce alcun valore.

Sintassi

class Writer extends RecursiveAction {
   @Override
   protected void compute() { }
}

RecursiveTask

RecursiveTask rappresenta un'attività che restituisce un valore.

Sintassi

class Sum extends RecursiveTask<Long> {
   @Override
   protected Long compute() { return null; }
}

Esempio

Il seguente programma TestThread mostra l'utilizzo del framework Fork-Join in un ambiente basato su thread.

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

public class TestThread {

   public static void main(final String[] arguments) throws InterruptedException, 
      ExecutionException {
      
      int nThreads = Runtime.getRuntime().availableProcessors();
      System.out.println(nThreads);
      
      int[] numbers = new int[1000]; 

      for(int i = 0; i < numbers.length; i++) {
         numbers[i] = i;
      }

      ForkJoinPool forkJoinPool = new ForkJoinPool(nThreads);
      Long result = forkJoinPool.invoke(new Sum(numbers,0,numbers.length));
      System.out.println(result);
   }  

   static class Sum extends RecursiveTask<Long> {
      int low;
      int high;
      int[] array;

      Sum(int[] array, int low, int high) {
         this.array = array;
         this.low   = low;
         this.high  = high;
      }

      protected Long compute() {
         
         if(high - low <= 10) {
            long sum = 0;
            
            for(int i = low; i < high; ++i) 
               sum += array[i];
               return sum;
         } else {	    	
            int mid = low + (high - low) / 2;
            Sum left  = new Sum(array, low, mid);
            Sum right = new Sum(array, mid, high);
            left.fork();
            long rightResult = right.compute();
            long leftResult  = left.join();
            return leftResult + rightResult;
         }
      }
   }
}

Questo produrrà il seguente risultato.

Produzione

32
499500

Un'interfaccia java.util.concurrent.BlockingQueue è una sottointerfaccia dell'interfaccia Queue e supporta inoltre operazioni come l'attesa che la coda diventi non vuota prima di recuperare un elemento e attendere che lo spazio diventi disponibile nella coda prima di memorizzare un elemento .

Metodi BlockingQueue

Sr.No. Metodo e descrizione
1

boolean add(E e)

Inserisce l'elemento specificato in questa coda se è possibile farlo immediatamente senza violare le limitazioni di capacità, restituendo true in caso di esito positivo e generando un'eccezione IllegalStateException se non è attualmente disponibile spazio.

2

boolean contains(Object o)

Restituisce vero se questa coda contiene l'elemento specificato.

3

int drainTo(Collection<? super E> c)

Rimuove tutti gli elementi disponibili da questa coda e li aggiunge alla raccolta data.

4

int drainTo(Collection<? super E> c, int maxElements)

Rimuove al massimo il numero specificato di elementi disponibili da questa coda e li aggiunge alla raccolta data.

5

boolean offer(E e)

Inserisce l'elemento specificato in questa coda se è possibile farlo immediatamente senza violare le limitazioni di capacità, restituendo true in caso di successo e false se non è attualmente disponibile spazio.

6

boolean offer(E e, long timeout, TimeUnit unit)

Inserisce l'elemento specificato in questa coda, attendendo fino al tempo di attesa specificato, se necessario, affinché lo spazio diventi disponibile.

7

E poll(long timeout, TimeUnit unit)

Recupera e rimuove la testata di questa coda, aspettando fino al tempo di attesa specificato, se necessario, che un elemento diventi disponibile.

8

void put(E e)

Inserisce l'elemento specificato in questa coda, aspettando se necessario che lo spazio diventi disponibile.

9

int remainingCapacity()

Restituisce il numero di elementi aggiuntivi che questa coda può idealmente (in assenza di vincoli di memoria o di risorse) accettare senza bloccare, oppure Integer.MAX_VALUE se non esiste un limite intrinseco.

10

boolean remove(Object o)

Rimuove una singola istanza dell'elemento specificato da questa coda, se presente.

11

E take()

Recupera e rimuove la testa di questa coda, aspettando se necessario fino a quando un elemento non diventa disponibile.

Esempio

Il seguente programma TestThread mostra l'utilizzo dell'interfaccia BlockingQueue in un ambiente basato su thread.

import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class TestThread {

   public static void main(final String[] arguments) throws InterruptedException {
      BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(10);

      Producer producer = new Producer(queue);
      Consumer consumer = new Consumer(queue);

      new Thread(producer).start();
      new Thread(consumer).start();

      Thread.sleep(4000);
   }  


   static class Producer implements Runnable {
      private BlockingQueue<Integer> queue;

      public Producer(BlockingQueue queue) {
         this.queue = queue;
      }

      @Override
      public void run() {
         Random random = new Random();

         try {
            int result = random.nextInt(100);
            Thread.sleep(1000);
            queue.put(result);
            System.out.println("Added: " + result);
            
            result = random.nextInt(100);
            Thread.sleep(1000);
            queue.put(result);
            System.out.println("Added: " + result);
            
            result = random.nextInt(100);
            Thread.sleep(1000);
            queue.put(result);
            System.out.println("Added: " + result);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }	   
   }

   static class Consumer implements Runnable {
      private BlockingQueue<Integer> queue;

      public Consumer(BlockingQueue queue) {
         this.queue = queue;
      }
      
      @Override
      public void run() {
         
         try {
            System.out.println("Removed: " + queue.take());
            System.out.println("Removed: " + queue.take());
            System.out.println("Removed: " + queue.take());
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }
}

Questo produrrà il seguente risultato.

Produzione

Added: 52
Removed: 52
Added: 70
Removed: 70
Added: 27
Removed: 27

Un'interfaccia java.util.concurrent.ConcurrentMap è una sottointerfaccia dell'interfaccia Map, supporta operazioni atomiche sulla variabile mappa sottostante. Ha metodi get e set che funzionano come letture e scritture su variabili volatili. Cioè, un insieme ha una relazione accade prima con qualsiasi successivo get sulla stessa variabile. Questa interfaccia garantisce la sicurezza dei thread e le garanzie di atomicità.

Metodi ConcurrentMap

Sr.No. Metodo e descrizione
1

default V compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)

Tenta di calcolare una mappatura per la chiave specificata e il suo valore mappato corrente (o null se non esiste una mappatura corrente).

2

default V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)

Se la chiave specificata non è già associata a un valore (o è mappata su null), tenta di calcolarne il valore utilizzando la funzione di mappatura data e la inserisce in questa mappa a meno che null.

3

default V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)

Se il valore per la chiave specificata è presente e non nullo, tenta di calcolare una nuova mappatura data la chiave e il suo valore mappato corrente.

4

default void forEach(BiConsumer<? super K,? super V> action)

Esegue l'azione data per ciascuna voce in questa mappa fino a quando tutte le voci non sono state elaborate o l'azione genera un'eccezione.

5

default V getOrDefault(Object key, V defaultValue)

Restituisce il valore a cui è mappata la chiave specificata o defaultValue se questa mappa non contiene alcuna mappatura per la chiave.

6

default V merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction)

Se la chiave specificata non è già associata a un valore o è associata a null, la associa al valore non null specificato.

7

V putIfAbsent(K key, V value)

Se la chiave specificata non è già associata a un valore, associarla al valore dato.

8

boolean remove(Object key, Object value)

Rimuove la voce per una chiave solo se attualmente mappata a un determinato valore.

9

V replace(K key, V value)

Sostituisce la voce per una chiave solo se attualmente mappata su un valore.

10

boolean replace(K key, V oldValue, V newValue)

Sostituisce la voce per una chiave solo se attualmente mappata a un determinato valore.

11

default void replaceAll(BiFunction<? super K,? super V,? extends V> function)

Sostituisce il valore di ogni voce con il risultato del richiamo della funzione data su quella voce finché tutte le voci non sono state elaborate o la funzione genera un'eccezione.

Esempio

Il seguente programma TestThread mostra l'utilizzo di ConcurrentMap rispetto a HashMap.

import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class TestThread {

   public static void main(final String[] arguments) {
      Map<String,String> map = new ConcurrentHashMap<String, String>();

      map.put("1", "One");
      map.put("2", "Two");
      map.put("3", "Three");
      map.put("5", "Five");
      map.put("6", "Six");

      System.out.println("Initial ConcurrentHashMap: " + map);
      Iterator<String> iterator = map.keySet().iterator();

      try { 
         
         while(iterator.hasNext()) {
            String key = iterator.next();
            
            if(key.equals("3")) {
               map.put("4", "Four");
            }
         }
      } catch(ConcurrentModificationException cme) {
         cme.printStackTrace();
      }
      System.out.println("ConcurrentHashMap after modification: " + map);

      map = new HashMap<String, String>();

      map.put("1", "One");
      map.put("2", "Two");
      map.put("3", "Three");
      map.put("5", "Five");
      map.put("6", "Six");

      System.out.println("Initial HashMap: " + map);
      iterator = map.keySet().iterator();

      try {
         
         while(iterator.hasNext()) {
            String key = iterator.next();
            
            if(key.equals("3")) {
               map.put("4", "Four");
            }
         }
         System.out.println("HashMap after modification: " + map);
      } catch(ConcurrentModificationException cme) {
         cme.printStackTrace();
      }
   }  
}

Questo produrrà il seguente risultato.

Produzione

Initial ConcurrentHashMap: {1 = One, 2 = Two, 3 = Three, 5 = Five, 6 = Six}
ConcurrentHashMap after modification: {1 = One, 2 = Two, 3 = Three, 4 = Four, 5 = Five, 6 = Six}
Initial HashMap: {1 = One, 2 = Two, 3 = Three, 5 = Five, 6 = Six}
java.util.ConcurrentModificationException
	at java.util.HashMap$HashIterator.nextNode(Unknown Source)
	at java.util.HashMap$KeyIterator.next(Unknown Source)
	at TestThread.main(TestThread.java:48)

Un'interfaccia java.util.concurrent.ConcurrentNavigableMap è una sottointerfaccia dell'interfaccia ConcurrentMap e supporta le operazioni NavigableMap e in modo ricorsivo per le sue mappe secondarie navigabili e le corrispondenze approssimative.

Metodi ConcurrentMap

Sr.No. Metodo e descrizione
1

NavigableSet<K> descendingKeySet()

Restituisce una visualizzazione NavigableSet in ordine inverso delle chiavi contenute in questa mappa.

2

ConcurrentNavigableMap<K,V> descendingMap()

Restituisce una vista in ordine inverso delle mappature contenute in questa mappa.

3

ConcurrentNavigableMap<K,V> headMap(K toKey)

Restituisce una visualizzazione della porzione di questa mappa le cui chiavi sono strettamente inferiori a toKey.

4

ConcurrentNavigableMap<K,V> headMap(K toKey, boolean inclusive)

Restituisce una visualizzazione della parte di questa mappa le cui chiavi sono minori di (o uguali a, se inclusive è vero) toKey.

5

NavigableSet<K> keySet()

Restituisce una vista NavigableSet delle chiavi contenute in questa mappa.

6

NavigableSet<K> navigableKeySet()

Restituisce una vista NavigableSet delle chiavi contenute in questa mappa.

7

ConcurrentNavigableMap<K,V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive)

Restituisce una visualizzazione della parte di questa mappa le cui chiavi vanno da fromKey a toKey.

8

ConcurrentNavigableMap<K,V> subMap(K fromKey, K toKey)

Restituisce una visualizzazione della parte di questa mappa le cui chiavi vanno da fromKey, inclusive, a toKey, exclusive.

9

ConcurrentNavigableMap<K,V> tailMap(K fromKey)

Restituisce una visualizzazione della parte di questa mappa le cui chiavi sono maggiori o uguali a fromKey.

10

ConcurrentNavigableMap<K,V> tailMap(K fromKey, boolean inclusive)

Restituisce una vista della parte di questa mappa le cui chiavi sono maggiori di (o uguali a, se inclusive è vero) fromKey.

Esempio

Il seguente programma TestThread mostra l'utilizzo di ConcurrentNavigableMap.

import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;

public class TestThread {

   public static void main(final String[] arguments) {
      ConcurrentNavigableMap<String,String> map =
         new ConcurrentSkipListMap<String, String>();

      map.put("1", "One");
      map.put("2", "Two");
      map.put("3", "Three");
      map.put("5", "Five");
      map.put("6", "Six");

      System.out.println("Initial ConcurrentHashMap: "+map);
      System.out.println("HeadMap(\"2\") of ConcurrentHashMap: "+map.headMap("2"));
      System.out.println("TailMap(\"2\") of ConcurrentHashMap: "+map.tailMap("2"));
      System.out.println(
         "SubMap(\"2\", \"4\") of ConcurrentHashMap: "+map.subMap("2","4"));
   }  
}

Questo produrrà il seguente risultato.

Produzione

Initial ConcurrentHashMap: {1 = One, 2 = Two, 3 = Three, 5 = Five, 6 = Six}
HeadMap("2") of ConcurrentHashMap: {1 = One}
TailMap("2") of ConcurrentHashMap: {2 = Two, 3 = Three, 5 = Five, 6 = Six}
SubMap("2", "4") of ConcurrentHashMap: {2 = Two, 3 = Three}