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

Eine Einführung in gleichzeitige Erfassungs-APIs in Java

Neben der Java-Sammlungs-API handelt es sich bei den gleichzeitigen Sammlungs-APIs um eine Reihe von Sammlungs-APIs, die speziell für den synchronisierten Multithread-Zugriff entwickelt und optimiert wurden. Sie sind unter java.util.concurrent gruppiert Paket. Dieser Artikel gibt einen Überblick und stellt seine Verwendung anhand eines geeigneten Beispielszenarios vor.

Ein Überblick

Java hat von Anfang an Multithreading und Parallelität unterstützt. Die Threads werden erstellt, indem entweder Runnable implementiert wird Schnittstelle oder Erweiterung des Threads Klasse. Die Synchronisierung wird durch das Schlüsselwort synchronization erreicht . Java stellt auch den Mechanismus für die Kommunikation zwischen den Threads bereit. Dies wird mit Hilfe von notify() erreicht und warten() Methoden, die Teil des Objekts sind Klasse. Obwohl diese innovativen Multithreading-Techniken Teil einiger der herausragenden Funktionen von Java sind, erfüllen sie etwas nicht die Anforderungen eines Programmierers, der eine intensive Multithreading-Fähigkeit von Anfang an benötigt. Dies liegt daran, dass ein nebenläufiges Programm mehr braucht, als nur in der Lage zu sein, Threads zu erstellen und einige rudimentäre Manipulationen vorzunehmen. Es erfordert viele High-Level-Features wie Thread-Pools, Ausführungsmanager, Semaphore und so weiter.

Vorhandenes Sammlungs-Framework

Java verfügt bereits über ein vollständiges Collection-Framework. Die Sammlungen sind sehr gut in dem, was sie tun, und können auch in Java-Thread-Anwendungen verwendet werden. Außerdem gibt es ein Schlüsselwort namens synchronized , um sie threadsicher zu machen. Obwohl es oberflächlich scheinen mag, dass sie gut für Multithreading verwendet werden können, ist die Art und Weise, wie die Thread-Sicherheit erreicht wird, der Hauptengpass bei ihrer gleichzeitigen Implementierung. Abgesehen von der expliziten Synchronisation sind sie nicht von Anfang an unter dem Paradigma der gleichzeitigen Implementierung konzipiert. Die Synchronisierung dieser Sammlungen wird erreicht, indem alle Zugriffe auf den Status der Sammlung serialisiert werden. Dies bedeutet, dass wir zwar eine gewisse Parallelität haben, aber aufgrund der zugrunde liegenden serialisierten Verarbeitung nach einem Prinzip funktioniert, das eigentlich das Gegenteil ist. Die Serialisierung beeinträchtigt die Leistung stark, insbesondere wenn mehrere Threads um die sammlungsweite Sperre konkurrieren.

Neue Sammlungs-APIs

Gleichzeitige Sammlungs-APIs sind eine Ergänzung zu Java ab Version 5 und sind Teil des Pakets namens java.util.concurrent . Sie sind eine Verbesserung bestehender Sammlungs-APIs und wurden für den gleichzeitigen Zugriff von mehreren Threads entwickelt. Beispiel:ConcurrentHashMap ist eigentlich die Klasse, die wir brauchen, wenn wir eine synchronisierte Hash-basierte Map verwenden wollen Implementierung. Ebenso, wenn wir eine Traversal-dominante, Thread-sichere Liste wollen , können wir tatsächlich die CopyOnWriterArrayList verwenden Klasse. Die neue ConcurrentMap -Schnittstelle bietet eine Reihe zusammengesetzter Aktionen unter einer einzigen Methode, wie z. B. putIfPresent , computeIfPresent , ersetzen , zusammenführen , und so weiter. Es gibt viele solcher Klassen, die innerhalb des neuen Concurrent-Collection-Frameworks liegen. Um nur einige zu nennen:ArrayBlockingQueue , ConcurrentLinkedDeque , ConcurrentLinkedQueue , ConcurrentSkipListMap , ConcurrentSkipListSet , CopyOnWriteArraySet , DelayQueue , LinkedBlockingDeque , LinkedBlockingQueue , LinkedTransferQueue , PriorityBlockingQueue , SynchronousQueue , und andere.

Warteschlangen

Die Sammlungstypen, z. B. Warteschlange und BlockingQueue , kann verwendet werden, um ein Element zeitweise zu halten und auf seine Wiedergewinnung in einer FIFO-Weise zur Verarbeitung zu warten. ConcurrentLinkQueue , andererseits ist eine traditionelle FIFO-Warteschlange, die als unbegrenzte, Thread-sichere Warteschlange basierend auf verknüpften Knoten implementiert ist. PriorityBlockingQueue ist eine unbegrenzte Sperrwarteschlange, die dieselben Ordnungsnormen verwendet wie die nicht gleichzeitige PriorityQueue und liefert blockierende Abrufvorgänge.

Karten

Wenn in älteren Sammlungsklassen eine Synchronisierung angewendet wird, hält sie Sperren für die Dauer jeder Operation. Es gibt Operationen wie get Methode von HashMap oder enthält Methode von List , die beim Aufrufen komplizierte Berechnungen hinter den Kulissen beinhalten. Um beispielsweise ein bestimmtes Element in einer Liste zu finden, wird automatisch gleich aufgerufen Methode. Dieses Verfahren erfordert bestimmte Berechnungen, um jedes Element auf der Liste zu vergleichen; Es kann lange dauern, bis die Aufgabe abgeschlossen ist. Dies ist in einer Hash-basierten Sammlung schlimmer. Wenn die Elemente in den Hash-Maps ungleichmäßig verteilt sind, kann das Durchlaufen einer langen Liste und das Aufrufen von equals sehr lange dauern. Dies ist ein Problem, da es die Gesamtleistung der Anwendung beeinträchtigen kann.

Im Gegensatz zu HashMap , ConcurrentHashMap verwendet eine ganz andere Strategie. Anstatt für jede synchronisierte Methode eine gemeinsame Sperre bereitzustellen, verwendet sie eine Technik namens Sperren von Sperren . Dies ist eine bessere Lösung für Parallelität und Skalierbarkeit. Lock-Stripping verwendet separate Locks für separate Buckets. Infolgedessen wird Thread-Konkurrenz von der zugrunde liegenden Datenstruktur entkoppelt und stattdessen dem Bucket auferlegt. Zum Beispiel verwendet die Implementierung von ConcurrentHashMap ein Array von 16 Sperren – von denen jede 1/16 der Hash-Buckets schützt; Bucket N wird von Lock N mod 16 bewacht … dies reduziert die Nachfrage nach einem bestimmten Lock um etwa den Faktor 16. Aufgrund dieser Technik kann ConcurrentHashMap unterstützt standardmäßig mindestens 16 gleichzeitige Schreiber und mehr können auf Anfrage untergebracht werden.

CopyOnWriterArrayList

Es ist eine gute Alternative zur synchronisierten Liste und erfordert nicht, dass Sie während der Iteration einen Sperrmechanismus anwenden. Die Iteratoren behalten zu Beginn der Iteration einen Verweis auf das unterstützende Array bei und ändern es nicht. Daher ist eine kurze Synchronisation erforderlich, um den Inhalt des Arrays abzurufen. Mehrere Threads können auf die Sammlung zugreifen, ohne sich gegenseitig zu stören. Sogar Modifikationen aus mehreren Threads unterliegen keinen Konflikten. Es gibt ein gesetztes Gegenstück zu dieser Array-Liste namens CopyOnWriterSet , die zum Ersetzen des synchronisierten Set verwendet werden kann auf Parallelitätsbedarf.

Ein kurzes Beispiel

Es gibt viele Klassen in der gleichzeitigen Sammlung. Ihre Verwendung ist für jemanden, der mit dem älteren Sammlungsrahmen vertraut ist, nicht so schwierig. Der Vollständigkeit halber ist hier ein Beispiel, das einen Einblick in die Verwendung in der Java-Programmierung gibt.

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.");
         }
      }
   }
}

Schlussfolgerung

Der vielleicht größte Vorteil der Verwendung der gleichzeitigen Sammlungsklassen ist ihre Skalierbarkeit und ihr geringes Risiko. Die gleichzeitigen Erfassungs-APIs von Java stellen eine Reihe von Klassen bereit, die speziell für die Handhabung gleichzeitiger Operationen entwickelt wurden. Diese Klassen sind Alternativen zum Java Collection Framework und bieten ähnliche Funktionen, außer mit der zusätzlichen Unterstützung der Parallelität. Daher ist die Lernkurve für den Programmierer, der das Java Collection Framework bereits kennt, nahezu flach. Die Klassen sind im Paket java.util.concurrent definiert . Hier habe ich versucht, einen Überblick zu geben, um loszulegen und die Sammlungs-APIs wo immer nötig zu verwenden.

Referenzen

  • Java-API-Dokumentation
  • Goetz, Brian und Tim Peierls. Java-Parallelität in der Praxis . Pearson, 2013.