Database
 sql >> Database >  >> RDS >> Database

Un'introduzione alle API di raccolta simultanee in Java

Le API di raccolta simultanee, a parte l'API di raccolta Java, sono un insieme di API di raccolta progettate e ottimizzate specificamente per l'accesso multithread sincronizzato. Sono raggruppati in java.util.concurrent pacchetto. Questo articolo fornisce una panoramica e ne introduce l'utilizzo utilizzando uno scenario di esempio appropriato.

Una panoramica

Java ha supportato il multithreading e la concorrenza sin dall'inizio. I thread vengono creati implementando Eseguibile interfaccia o estendendo il Thread classe. La sincronizzazione è ottenuta dalla parola chiave denominata sincronizzazione . Java fornisce anche il meccanismo per la comunicazione tra i thread. Ciò si ottiene con l'aiuto di notify() e aspetta() metodi, che fanno parte dell'Oggetto classe. Sebbene queste innovative tecniche di multithreading facciano parte di alcune delle eccellenti caratteristiche di Java, non soddisfano in qualche modo la necessità di un programmatore che richieda un'intensa capacità di multithreading pronta all'uso. Questo perché un programma simultaneo ha bisogno di qualcosa di più della semplice capacità di creare thread ed eseguire alcune manipolazioni rudimentali. Richiede molte funzionalità di alto livello come pool di thread, gestori di esecuzione, semafori e così via.

Framework di raccolta esistente

Java ha già un framework di raccolta completo. Le raccolte sono molto brave in quello che fanno e possono essere utilizzate anche nelle applicazioni di thread Java. Inoltre, esiste una parola chiave, chiamata sincronizzata , per renderli thread-safe. Sebbene superficialmente possa sembrare utile da usare nel multithreading, il modo in cui viene raggiunta la sicurezza del thread è il principale collo di bottiglia nella sua implementazione simultanea. A parte la sincronizzazione esplicita, non sono progettati fin dall'inizio nel paradigma dell'implementazione simultanea. La sincronizzazione di queste raccolte si ottiene serializzando tutti gli accessi allo stato della raccolta. Ciò significa che, sebbene potremmo avere una certa concorrenza, a causa dell'elaborazione serializzata sottostante funziona secondo un principio che in realtà è l'opposto. La serializzazione ha un impatto pesante sulle prestazioni, soprattutto quando più thread competono per il blocco a livello di raccolta.

Nuove API di raccolta

Le API di raccolta simultanee sono un'aggiunta a Java dalla versione 5 e fanno parte del pacchetto chiamato java.util.concurrent . Sono un miglioramento delle API di raccolta esistenti e sono state progettate per l'accesso simultaneo da più thread. Ad esempio, ConcurrentHashMap è in realtà la classe di cui abbiamo bisogno quando vogliamo usare una Mappa sincronizzata basata su hash implementazione. Allo stesso modo, se vogliamo un Elenco traversal-dominant e thread-safe , possiamo effettivamente utilizzare CopyOnWriterArrayList classe. La nuova Mappa simultanea interfaccia fornisce una serie di azioni composte in un unico metodo, come putIfPresent , computeIfPresent , sostituisci , unire , e così via. Esistono molte classi di questo tipo che si trovano all'interno del nuovo framework di raccolta simultanea. Per citarne alcuni:ArrayBlockingQueue , ConcurrentLinkedDeque , Coda collegata simultanea , Mappa SkipList simultanea , Imposta lista di salto simultanea , CopyOnWriteArraySet , Coda di ritardo , LinkedBlockingDeque , LinkedBlockingQueue , LinkedTransferQueue , PrioritàBlockingQueue , Coda sincrona e altri.

Code

I tipi di raccolta, come Coda e Coda di blocco , può essere utilizzato per trattenere temporaneamente un elemento, in attesa del recupero in modalità FIFO per l'elaborazione. Coda di collegamento simultanea , d'altra parte, è una coda FIFO tradizionale implementata come una coda thread-safe illimitata basata su nodi collegati. PrioritàBlockingQueue è una coda di blocco illimitata che utilizza le stesse norme di ordinamento di quella di PriorityQueue non simultanea e il blocco delle operazioni di recupero delle forniture.

Mappe

Nelle classi di raccolta precedenti, quando viene applicata la sincronizzazione, mantiene i blocchi per la durata di ogni operazione. Ci sono operazioni, come get metodo di HashMap o contiene metodo di Elenco , che implicano un intricato calcolo dietro le quinte quando invocato. Ad esempio, per trovare un elemento specifico in un elenco, richiama automaticamente uguale metodo. Questo metodo richiede un certo calcolo per confrontare ogni elemento dell'elenco; potrebbe volerci molto tempo per completare l'attività. Questo è peggio in una raccolta basata su hash. Se gli elementi nelle mappe hash sono distribuiti in modo non uniforme, attraversare un lungo elenco e chiamare equals può richiedere molto tempo. Questo è un problema perché potrebbe influire sulle prestazioni complessive dell'applicazione.

A differenza di HashMap , Mappa hash simultanea utilizza una strategia completamente diversa. Invece di fornire un blocco comune per ogni metodo sincronizzato, utilizza una tecnica chiamata lock stripping . Questa è una soluzione migliore sia per la concorrenza che per la scalabilità. La rimozione delle serrature utilizza serrature separate per secchi separati. Di conseguenza, la contesa dei thread viene disaccoppiata dalla struttura dei dati sottostante e invece imposta al bucket. Ad esempio, l'implementazione di ConcurrentHashMap utilizza un array di 16 lock, ognuno dei quali protegge 1/16 degli hash bucket; il secchio N è protetto dalla serratura N mod 16... questo riduce la richiesta di una data serratura di circa un fattore 16. È grazie a questa tecnica che ConcurrentHashMap supporta almeno 16 writer simultanei per impostazione predefinita e altri possono essere ospitati su richiesta.

Copia suWriterArrayList

È un'ottima alternativa all'Elenco sincronizzato e non richiede l'applicazione di un meccanismo di blocco durante l'iterazione. Gli iteratori mantengono un riferimento all'array di supporto all'inizio dell'iterazione e non lo modificano. Pertanto, è necessaria una breve sincronizzazione per ottenere il contenuto dell'array. Più thread possono accedere alla raccolta senza interferire tra loro. Anche le modifiche da più thread non subiscono contese. Esiste una controparte impostata di questo elenco di array, chiamata CopyOnWriterSet , che può essere utilizzato per sostituire Set sincronizzato sulla necessità di concorrenza.

Un rapido esempio

Ci sono molte classi nella raccolta simultanea. Il loro utilizzo non è così difficile per chiunque abbia familiarità con il vecchio framework di raccolta. Per completezza, ecco un esempio per dare uno sguardo ai suoi usi nella programmazione Java.

package org.mano.example;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class ProducerConsumerDemo {
   static BlockingQueue<Integer> queue = new
      LinkedBlockingQueue<>(5);
   public static void main(String[] args) throws
         InterruptedException {
      int noOfProducers = 7;
      int noOfConsumers = 9;
      for (inti = 0; i < noOfProducers; i++) {
         new Thread(new Producer(), "PRODUCER").start();
      }
      for (int i = 0; i < noOfConsumers; i++) {
         new Thread(new Consumer(), "CONSUMER").start();
      }
      System.exit(0);
   }
   static class Producer implements Runnable {
      Random random = new Random();
      public void run() {
         try {
            int num = random.nextInt(100);
            queue.put(num);
            System.out.println("Produced: " + num
               + " Queue size : "+ queue.size());
            Thread.sleep(100);
         } catch (InterruptedException ex) {
            System.out.println("Producer is interrupted.");
         }
      }
   }
   static class Consumer implements Runnable {
      public void run() {
         try {
            System.out.println("Consumed: " + queue.take()
               + " Queue size : "+ queue.size());
            Thread.sleep(100);
         } catch (InterruptedException ex) {
            System.out.println("Consumer is interrupted.");
         }
      }
   }
}

Conclusione

Forse il più grande vantaggio dell'utilizzo delle classi di raccolta simultanee è la loro scalabilità e basso rischio. Le API di raccolta simultanee di Java forniscono una gamma di classi progettate specificamente per gestire operazioni simultanee. Queste classi sono alternative a Java Collection Framework e forniscono funzionalità simili tranne che con il supporto aggiuntivo della concorrenza. Pertanto, la curva di apprendimento per il programmatore che già conosce Java Collection Framework è quasi piatta. Le classi sono definite nel pacchetto java.util.concurrent . Qui, ho cercato di fornire una panoramica per iniziare e utilizzare le API di raccolta ove necessario.

Riferimenti

  • Documentazione API Java
  • Goetz, Brian e Tim Peierls. La concorrenza Java nella pratica . Pearson, 2013.