De parallella insamlings-API:erna, förutom Java Collection API, är en uppsättning samlings-API:er som är designade och optimerade specifikt för synkroniserad flertrådad åtkomst. De är grupperade under java.util.concurrent paket. Den här artikeln ger en översikt och introducerar dess användning genom att använda ett lämpligt exempelscenario.
En översikt
Java har stött multithreading och samtidighet från starten. Trådarna skapas genom att antingen implementera Runnable gränssnitt eller utöka tråden klass. Synkroniseringen uppnås med nyckelordet synkronisering . Java tillhandahåller också mekanismen för kommunikation mellan trådarna. Detta uppnås med hjälp av notify() och wait() metoder, som är en del av Objektet klass. Även om dessa innovativa flertrådstekniker är en del av några av de utmärkta funktionerna i Java, faller de något kort när det gäller att tillgodose behovet av en programmerare som kräver intensiv multitrådskapacitet direkt. Detta beror på att ett samtidigt program behöver mer än att bara kunna skapa trådar och göra några rudimentära manipulationer. Det kräver många funktioner på hög nivå som trådpooler, exekveringshanterare, semaforer och så vidare.
Befintligt samlingsramverk
Java har redan ett komplett samlingsramverk. Samlingarna är mycket bra på vad de gör och kan också användas i Java-trådsapplikationer. Det finns också ett nyckelord som kallas synkroniserat , för att göra dem trådsäkra. Även om det ytligt kan tyckas att de är trevliga att användas i multithreading, är det sätt på vilket trådsäkerheten uppnås den huvudsakliga flaskhalsen i dess samtidiga implementering. Förutom explicit synkronisering är de inte designade under paradigmet med samtidig implementering från början. Synkroniseringen av dessa samlingar uppnås genom att serialisera all åtkomst till samlingens tillstånd. Detta betyder att, även om vi kan ha en viss samtidighet, på grund av underliggande serialiserad bearbetning fungerar det enligt en princip som faktiskt är motsatsen. Serialisering påverkar prestandan hårt, särskilt när flera trådar tävlar om det samlingsomfattande låset.
Nya samlings-API:er
API:er för samtidig insamling är ett tillägg till Java från version 5 och är en del av paketet som heter java.util.concurrent . De är en förbättring av befintliga samlings-API:er och har designats för samtidig åtkomst från flera trådar. Till exempel ConcurrentHashMap är faktiskt den klass vi behöver när vi vill använda en synkroniserad hash-baserad karta genomförande. På liknande sätt, om vi vill ha en övergångsdominant, trådsäker lista , vi kan faktiskt använda CopyOnWriterArrayList klass. Den nya ConcurrentMap gränssnittet tillhandahåller ett antal sammansatta åtgärder under en enda metod, såsom putIfPresent , computeIfPresent , ersätt , sammanfoga , och så vidare. Det finns många sådana klasser som ligger inom det nya ramverket för samtidig insamling. För att nämna några:ArrayBlockingQueue , ConcurrentLinkedDeque , ConcurrentLinkedQueue , ConcurrentSkipListMap , ConcurrentSkipListSet , CopyOnWriteArraySet , DelayQueue , LinkedBlockingDeque , LinkedBlockingQueue , LinkedTransferQueue , PriorityBlockingQueue , SynchronousQueue , och andra.
Köer
Samlingstyperna, till exempel Kö och BlockingQueue , kan användas för att tillfälligt hålla ett element, väntar på hämtning på ett FIFO-sätt för bearbetning. ConcurrentLinkQueue , å andra sidan, är en traditionell FIFO-kö implementerad som en obegränsad, trådsäker kö baserad på länkade noder. PriorityBlockingQueue är en obegränsad blockeringskö som använder samma ordningsnormer som den för icke-samtidiga PriorityQueue och förnödenheter som blockerar hämtning.
Kartor
I äldre samlingsklasser när synkronisering tillämpas, håller den lås under varje operations varaktighet. Det finns operationer, till exempel get metod för HashMap eller innehåller metod för Lista , som involverar komplicerad beräkning bakom scenen när den anropas. Till exempel, för att hitta ett specifikt element i en lista, anropar det automatiskt lika metod. Denna metod kräver viss beräkning för att jämföra varje element på listan; det kan ta lång tid att slutföra uppgiften. Detta är värre i en hash-baserad samling. Om elementen i hash-kartorna är ojämnt fördelade kan det ta mycket lång tid att gå igenom en lång lista och anropa lika. Det här är ett problem eftersom det kan påverka programmets övergripande prestanda.
Till skillnad från HashMap , ConcurrentHashMap använder en helt annan strategi. Istället för att tillhandahålla ett gemensamt lås för varje synkroniserad metod använder den en teknik som kallas lock stripping . Detta är en bättre lösning för både samtidighet och skalbarhet. Låsstrippning använder separata lås för separata hinkar. Som ett resultat av detta frikopplas trådkonflikter från den underliggande datastrukturen och läggs istället på hinken. Till exempel använder implementeringen av ConcurrentHashMap en array med 16 lås – vart och ett skyddar 1/16 av hash-hinkarna; skopan N skyddas av lås N mod 16... detta minskar behovet av ett givet lås med ungefär en faktor 16. Det är på grund av denna teknik som ConcurrentHashMap stöder minst 16 samtidiga skribenter som standard och fler kan tas emot på begäran.
CopyOnWriterArrayList
Det är ett bra alternativ till den synkroniserade listan och kräver inte att du använder en låsmekanism under iteration. Iteratorerna behåller en referens till backing-arrayen i början av iterationen och ändrar den inte. Därför kräver det en kort synkronisering för att få fram innehållet i arrayen. Flera trådar kan komma åt samlingen utan att störa varandra. Inte ens modifiering från flera trådar drabbas inte av diskussion. Det finns en uppsättning motsvarighet till denna arraylista, kallad CopyOnWriterSet , som kan användas för att ersätta synkroniserad Set om samtidighetsbehov.
Ett snabbt exempel
Det finns många klasser i den samtidiga samlingen. Användningen av dem är inte så svår för alla som är bekanta med den äldre samlingsramen. För fullständighetens skull är här ett exempel för att ge en inblick i dess användningsområden i Java-programmering.
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."); } } } }
Slutsats
Den kanske största fördelen med att använda de samtidiga insamlingsklasserna är deras skalbarhet och låga risk. De parallella insamlings-API:erna för Java tillhandahåller en rad klasser som är speciellt utformade för att hantera samtidiga operationer. Dessa klasser är alternativ till Java Collection Framework och tillhandahåller liknande funktionalitet förutom med ytterligare stöd för samtidighet. Därför är inlärningskurvan för programmeraren som redan känner till Java Collection Framework nästan platt. Klasserna är definierade i paketet java.util.concurrent . Här har jag försökt ge en översikt för att komma igång och använda samlings-API:erna där det behövs.
Referenser
- Java API-dokumentation
- Goetz, Brian och Tim Peierls. Java samtidighet i praktiken . Pearson, 2013.