Rozhraní API pro souběžné kolekce, kromě rozhraní Java Collection API, jsou sadou rozhraní API pro kolekce, která jsou navržena a optimalizována speciálně pro synchronizovaný vícevláknový přístup. Jsou seskupeny pod java.util.concurrent balík. Tento článek poskytuje přehled a představuje jeho použití pomocí vhodného příkladu scénáře.
Přehled
Java od svého počátku podporuje multithreading a souběžnost. Vlákna jsou vytvořena buď implementací Runable rozhraní nebo rozšíření vlákna třída. Synchronizace se provádí pomocí klíčového slova synchronizace . Java také poskytuje mechanismus pro komunikaci mezi vlákny. Toho je dosaženo pomocí notify() a čekejte() metody, které jsou součástí Objektu třída. Ačkoli tyto inovativní techniky multithreadingu jsou součástí některých vynikajících vlastností Javy, poněkud zaostávají v poskytování potřeb programátora, který vyžaduje intenzivní schopnost multithreadingu hned po vybalení. Je to proto, že souběžný program potřebuje více než jen schopnost vytvářet vlákna a provádět některé základní manipulace. Vyžaduje mnoho funkcí na vysoké úrovni, jako jsou fondy vláken, exekuční manažery, semafory a tak dále.
Stávající rámec sběru
Java již má plnohodnotný rámec pro kolekce. Kolekce jsou velmi dobré v tom, co dělají, a lze je použít také v aplikacích s vlákny Java. Existuje také klíčové slovo synchronizované , aby byly bezpečné pro vlákna. I když se povrchně může zdát, že jsou vhodné pro použití ve vícevláknech, způsob, jakým je dosaženo bezpečnosti vlákna, je hlavním úzkým hrdlem v jeho souběžné implementaci. Kromě explicitní synchronizace nejsou od začátku navrženy podle paradigmatu souběžné implementace. Synchronizace těchto kolekcí je dosaženo serializací veškerého přístupu ke stavu kolekce. To znamená, že i když můžeme mít určitou souběžnost, díky základnímu serializovanému zpracování funguje na principu, který je ve skutečnosti opačný. Serializace zatěžuje výkon, zvláště když o zámek pro celou kolekci soutěží více vláken.
Nová rozhraní API pro kolekce
Rozhraní API pro souběžné kolekce jsou doplňkem k Javě od verze 5 a jsou součástí balíčku s názvem java.util.concurrent . Jsou vylepšením existujících rozhraní API pro kolekce a byly navrženy pro souběžný přístup z více vláken. Například ConcurrentHashMap je ve skutečnosti třída, kterou potřebujeme, když chceme použít synchronizovanou mapu založenou na hash implementace. Podobně, pokud chceme seznam s dominantním a bezpečným procházením , ve skutečnosti můžeme použít CopyOnWriterArrayList třída. Nová ConcurrentMap rozhraní poskytuje řadu složených akcí v rámci jediné metody, jako je putIfPresent , computeIfPresent , nahradit , sloučit , a tak dále. Existuje mnoho takových tříd, které jsou v rámci nového rámce souběžných kolekcí. Jmenujme alespoň některé:ArrayBlockingQueue , ConcurrentLinkedDeque , ConcurrentLinkedQueue , ConcurrentSkipListMap , ConcurrentSkipListSet , CopyOnWriteArraySet , DelayQueue , LinkedBlockingDeque , LinkedBlockingQueue , LinkedTransferQueue , PriorityBlockingQueue , SynchronousQueue a další.
Fronty
Typy kolekcí, například Fronta a BlockingQueue , lze použít k dočasnému zadržení prvku, čeká se na vyhledání způsobem FIFO ke zpracování. ConcurrentLinkQueue , na druhé straně je tradiční fronta FIFO implementovaná jako neomezená fronta bezpečná pro vlákna založená na propojených uzlech. Fronta prioritního blokování je neomezená blokovací fronta, která používá stejné normy řazení jako nesouběžné PriorityQueue a zásoby blokující operace získávání.
Mapy
Ve starších třídách kolekce při použití synchronizace drží zámky po dobu trvání každé operace. Existují operace, jako je get metoda HashMap nebo obsahuje metoda Seznam , které zahrnují složité výpočty za scénou, když jsou vyvolány. Chcete-li například najít konkrétní prvek v seznamu, automaticky vyvolá rovná se metoda. Tato metoda vyžaduje určité výpočty pro porovnání každého prvku v seznamu; dokončení úkolu může trvat dlouho. To je horší v kolekci založené na hash. Pokud jsou prvky v hašovacích mapách rozmístěny nerovnoměrně, procházení dlouhého seznamu a volání rovných může trvat velmi dlouho. To je problém, protože to může ovlivnit celkový výkon aplikace.
Na rozdíl od HashMap , ConcurrentHashMap používá úplně jinou strategii. Místo poskytování společného zámku pro každou synchronizovanou metodu používá techniku zvanou odstranění zámku . Toto je lepší řešení pro souběžnost i škálovatelnost. Odizolování zámků používá samostatné zámky pro samostatné kbelíky. Výsledkem je, že spory podprocesů jsou odděleny od základní datové struktury a místo toho jsou uloženy na segment. Například implementace ConcurrentHashMap používá pole 16 zámků – z nichž každý chrání 1/16 kbelíků hash; kbelík N je střežen zámkem N mod 16...to snižuje poptávku po daném zámku přibližně o faktor 16. Díky této technice ConcurrentHashMap ve výchozím nastavení podporuje alespoň 16 souběžných zapisovačů a na vyžádání lze umístit více.
CopyOnWriterArrayList
Je to dobrá alternativa k synchronizovanému Seznamu a nevyžaduje, abyste během iterace použili uzamykací mechanismus. Iterátory si na začátku iterace zachovají odkaz na podpůrné pole a nemění ho. Proto vyžaduje krátkou synchronizaci získat obsah pole. Ke kolekci může přistupovat více vláken, aniž by se navzájem rušily. Ani modifikace z více vláken netrpí spory. K tomuto seznamu polí existuje protějšek sady, nazvaný CopyOnWriterSet , kterou lze nahradit synchronizovanou Set o potřebě souběžnosti.
Rychlý příklad
V souběžné kolekci je mnoho tříd. Jejich použití není pro nikoho obeznámeného se starším rámcem kolekcí tak obtížné. Pro úplnost uvádíme příklad, který poskytne náhled na jeho použití v programování 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."); } } } }
Závěr
Snad největší výhodou použití souběžných tříd kolekce je jejich škálovatelnost a nízké riziko. Rozhraní API pro souběžné kolekce Java poskytují řadu tříd, které jsou speciálně navrženy pro práci se souběžnými operacemi. Tyto třídy jsou alternativami k Java Collection Framework a poskytují podobnou funkčnost s výjimkou dodatečné podpory souběžnosti. Proto je křivka učení pro programátory, kteří již vědí o Java Collection Framework, téměř plochá. Třídy jsou definovány v balíčku java.util.concurrent . Zde jsem se pokusil poskytnout přehled, abyste mohli začít a používat sběrná API, kdykoli je to nutné.
Odkazy
- Dokumentace Java API
- Goetz, Brian a Tim Peierls. Java Concurrency v praxi . Pearson, 2013.