Współbieżne interfejsy API kolekcji, oprócz interfejsu Java Collection API, to zestaw interfejsów API kolekcji zaprojektowanych i zoptymalizowanych specjalnie pod kątem zsynchronizowanego dostępu wielowątkowego. Są one pogrupowane w java.util.concurrent pakiet. W tym artykule przedstawiono przegląd i wprowadzenie do jego użycia, korzystając z odpowiedniego przykładowego scenariusza.
Przegląd
Java od samego początku wspiera wielowątkowość i współbieżność. Wątki są tworzone przez implementację Runnable interfejs lub rozszerzenie wątku klasa. Synchronizację uzyskuje się za pomocą słowa kluczowego o nazwie synchronizacja . Java zapewnia również mechanizm komunikacji między wątkami. Osiąga się to za pomocą notify() i czekaj() metody, które są częścią Obiektu klasa. Chociaż te innowacyjne techniki wielowątkowości są częścią niektórych doskonałych funkcji Javy, to jednak nie zaspokajają potrzeb programisty, który wymaga intensywnej wielowątkowości zaraz po wyjęciu z pudełka. Dzieje się tak, ponieważ współbieżny program potrzebuje czegoś więcej niż tylko możliwości tworzenia wątków i wykonywania podstawowych manipulacji. Wymaga wielu funkcji wysokiego poziomu, takich jak pule wątków, menedżery wykonywania, semafory i tak dalej.
Istniejące ramy gromadzenia
Java ma już pełną strukturę kolekcji. Kolekcje są bardzo dobre w tym, co robią i mogą być również używane w aplikacjach wątków Java. Istnieje również słowo kluczowe o nazwie zsynchronizowane , aby były bezpieczne dla wątków. Chociaż powierzchownie może się wydawać, że są przyjemne w użyciu w wielowątkowości, sposób, w jaki osiąga się bezpieczeństwo wątków, jest głównym wąskim gardłem w jego współbieżnej implementacji. Poza jawną synchronizacją nie są one projektowane od początku w paradygmacie współbieżnej implementacji. Synchronizacja tych kolekcji jest osiągana przez serializację całego dostępu do stanu kolekcji. Oznacza to, że chociaż możemy mieć pewną współbieżność, ze względu na podstawowe przetwarzanie serializowane działa na zasadzie, która jest w rzeczywistości odwrotna. Serializacja ma duży wpływ na wydajność, zwłaszcza gdy wiele wątków konkuruje o blokadę całej kolekcji.
Nowe interfejsy API kolekcji
Współbieżne interfejsy API gromadzenia danych są dodatkiem do Javy od wersji 5 i są częścią pakietu o nazwie java.util.concurrent . Stanowią one ulepszenie istniejących interfejsów API kolekcji i zostały zaprojektowane z myślą o równoczesnym dostępie z wielu wątków. Na przykład ConcurrentHashMap jest w rzeczywistości klasą, której potrzebujemy, gdy chcemy użyć zsynchronizowanej Mapy opartej na hashu realizacja. Podobnie, jeśli chcemy, aby Lista była zdominowana przez przechodzenie i bezpieczna dla wątków , faktycznie możemy użyć CopyOnWriterArrayList klasa. Nowa ConcurrentMap interfejs udostępnia szereg działań złożonych w ramach jednej metody, takich jak putIfPresent , computeIfPresent , zamień , scal , i tak dalej. Istnieje wiele takich klas, które znajdują się w nowej strukturze współbieżnego gromadzenia danych. Aby wymienić tylko kilka:ArrayBlockingQueue , ConcurrentLinkedDeque , ConcurrentLinkedQueue , ConcurrentSkipListMap , Concurrent SkipListSet , CopyOnWriteArraySet , Opóźnienie kolejki , LinkedBlockingDeque , LinkedBlockingQueue , LinkedTransferQueue , Kolejka blokowania priorytetów , Synchroniczna kolejka i inne.
Kolejki
Typy kolekcji, takie jak Kolejka i Kolejka blokowania , może służyć do tymczasowego przytrzymania elementu, oczekiwanie na pobranie w sposób FIFO do przetworzenia. Równoczesna kolejka łączy , z drugiej strony, jest tradycyjną kolejką FIFO zaimplementowaną jako nieograniczona, bezpieczna wątkowo kolejka oparta na połączonych węzłach. Kolejka blokowania priorytetów to nieograniczona kolejka blokująca, która wykorzystuje te same normy porządkowania, co niewspółbieżna PriorityQueue i dostarcza blokujące operacje pobierania.
Mapy
W starszych klasach kolekcji, gdy stosowana jest synchronizacja, utrzymuje blokady na czas trwania każdej operacji. Istnieją operacje, takie jak pobierz metoda HashMap lub zawiera metoda Listy , które po wywołaniu wymagają skomplikowanych obliczeń za kulisami. Na przykład, aby znaleźć określony element na liście, automatycznie wywołuje równa się metoda. Ta metoda wymaga pewnych obliczeń, aby porównać każdy element na liście; wykonanie zadania może zająć dużo czasu. Gorzej jest w kolekcji opartej na hashu. Jeśli elementy na mapach mieszających są nierównomiernie rozmieszczone, przemierzanie długiej listy i wywoływanie równych może zająć bardzo dużo czasu. Jest to problem, ponieważ może wpłynąć na ogólną wydajność aplikacji.
W przeciwieństwie do HashMap , ConcurrentHashMap używa zupełnie innej strategii. Zamiast zapewniać wspólną blokadę dla każdej zsynchronizowanej metody, wykorzystuje technikę o nazwie lock stripping . Jest to lepsze rozwiązanie zarówno pod względem współbieżności, jak i skalowalności. Usuwanie zamków wykorzystuje oddzielne zamki dla oddzielnych kubełków. W rezultacie rywalizacja o wątki jest oddzielona od podstawowej struktury danych i zamiast tego narzucona na zasobnik. Na przykład implementacja ConcurrentHashMap wykorzystuje tablicę 16 blokad — z których każda chroni 1/16 wiader mieszających; wiadro N jest strzeżone przez blokadę N mod 16… to zmniejsza zapotrzebowanie na daną blokadę około 16 razy. To dzięki tej technice ConcurrentHashMap domyślnie obsługuje co najmniej 16 współbieżnych programów piszących, a na żądanie można dostosować ich więcej.
CopyOnWriterArrayList
Jest dobrą alternatywą dla zsynchronizowanej Listy i nie wymaga stosowania mechanizmu blokującego podczas iteracji. Iteratory zachowują odwołanie do tablicy bazowej na początku iteracji i nie zmieniają go. Dlatego wymaga krótkiej synchronizacji, aby uzyskać zawartość tablicy. Wiele wątków może uzyskać dostęp do kolekcji bez wzajemnego zakłócania się. Nawet modyfikacja z wielu wątków nie podlega rywalizacji. Istnieje odpowiednik tej listy tablic o nazwie CopyOnWriterSet , którego można użyć do zastąpienia zsynchronizowanego Zestawu na potrzeby współbieżności.
Szybki przykład
W kolekcji równoległej znajduje się wiele klas. Ich użycie nie jest trudne dla każdego, kto zna starsze ramy kolekcji. Ze względu na kompletność, oto przykład, który pozwala rzucić okiem na jego zastosowania w programowaniu 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."); } } } }
Wniosek
Być może największą zaletą korzystania z klas współbieżnych kolekcji jest ich skalowalność i niskie ryzyko. Współbieżne interfejsy API do zbierania danych w języku Java udostępniają szereg klas, które są specjalnie zaprojektowane do obsługi współbieżnych operacji. Te klasy są alternatywą dla Java Collection Framework i zapewniają podobną funkcjonalność, z wyjątkiem dodatkowej obsługi współbieżności. Dlatego krzywa uczenia się programisty, który już zna Java Collection Framework, jest prawie płaska. Klasy są zdefiniowane w pakiecie java.util.concurrent . W tym miejscu próbowałem przedstawić przegląd, aby rozpocząć i korzystać z interfejsów API kolekcji, gdy jest to konieczne.
Referencje
- Dokumentacja Java API
- Goetz, Brian i Tim Peierls. Współbieżność Java w praktyce . Pearson, 2013.