Java >> Java tutorial >  >> Tag >> synchronized

En introduktion til synkroniserede Java-samlinger

1. Oversigt

Samlingsrammen er en nøglekomponent i Java. Det giver et omfattende antal grænseflader og implementeringer, som giver os mulighed for at skabe og manipulere forskellige typer samlinger på en ligetil måde.

Selvom det generelt er simpelt at bruge almindelige usynkroniserede samlinger, kan det også blive en skræmmende og fejltilbøjelig proces, når du arbejder i flertrådede miljøer (også kendt som samtidig programmering).

Derfor giver Java-platformen stærk støtte til dette scenarie gennem forskellige synkroniseringsindpakninger implementeret i Samlingerne klasse.

Disse indpakninger gør det nemt at skabe synkroniserede visninger af de leverede samlinger ved hjælp af flere statiske fabriksmetoder.

I denne vejledning tager vi et dybt dyk ned i disse statiske synkroniseringsindpakninger. Vi vil også fremhæve forskellen mellem synkroniserede samlinger og samtidige samlinger .

2. synchronizedCollection() Metode

Den første synkroniseringsindpakning, som vi vil dække i denne runde-up, er synchronizedCollection() metode. Som navnet antyder, returnerer den en trådsikker samling, der er sikkerhedskopieret af den angivne Samling .

Nu, for at forstå mere klart, hvordan man bruger denne metode, lad os oprette en grundlæggende enhedstest:

Collection<Integer> syncCollection = Collections.synchronizedCollection(new ArrayList<>());
    Runnable listOperations = () -> {
        syncCollection.addAll(Arrays.asList(1, 2, 3, 4, 5, 6));
    };
    
    Thread thread1 = new Thread(listOperations);
    Thread thread2 = new Thread(listOperations);
    thread1.start();
    thread2.start();
    thread1.join();
    thread2.join();
    
    assertThat(syncCollection.size()).isEqualTo(12);
}

Som vist ovenfor er det meget enkelt at oprette en synkroniseret visning af den medfølgende samling med denne metode.

For at demonstrere, at metoden faktisk returnerer en trådsikker samling, opretter vi først et par tråde.

Derefter injicerer vi en Runnable ind i deres konstruktører i form af et lambda-udtryk. Lad os huske på, at Kørbar er en funktionel grænseflade, så vi kan erstatte den med et lambda-udtryk.

Til sidst tjekker vi bare, at hver tråd effektivt tilføjer seks elementer til den synkroniserede samling, så dens endelige størrelse er tolv.

3. synchronizedList() Metode

Ligeledes ligner synchronizedCollection() metode, kan vi bruge synchronizedList() wrapper for at oprette en synkroniseret liste .

Som vi kunne forvente, returnerer metoden en trådsikker visning af den angivne Liste :

List<Integer> syncList = Collections.synchronizedList(new ArrayList<>());

Ikke overraskende er brugen af ​​synchronizedList() metoden ser næsten identisk ud med dens modstykke på højere niveau, synchronizedCollection() .

Derfor, som vi lige gjorde i den forrige enhedstest, når vi først har oprettet en synkroniseret liste , vi kan skabe flere tråde. Efter at have gjort det, bruger vi dem til at få adgang til/manipulere mål-listen på en trådsikker måde.

Derudover, hvis vi ønsker at iterere over en synkroniseret samling og forhindre uventede resultater, bør vi eksplicit levere vores egen trådsikre implementering af løkken. Derfor kunne vi opnå det ved at bruge en synkroniseret blokere:

List<String> syncCollection = Collections.synchronizedList(Arrays.asList("a", "b", "c"));
List<String> uppercasedCollection = new ArrayList<>();
    
Runnable listOperations = () -> {
    synchronized (syncCollection) {
        syncCollection.forEach((e) -> {
            uppercasedCollection.add(e.toUpperCase());
        });
    }
};

I alle tilfælde, hvor vi skal iterere over en synkroniseret samling, bør vi implementere dette formsprog. Dette skyldes, at iterationen på en synkroniseret samling udføres gennem flere opkald til samlingen. Derfor skal de udføres som en enkelt atomoperation.

Brugen af ​​den synkroniserede blok sikrer atomiciteten af ​​operationen .

4. synchronizedMap() Metode

Samlingerne klasse implementerer en anden pæn synkroniseringsindpakning, kaldet synchronizedMap(). Vi kunne bruge det til nemt at oprette et synkroniseret kort .

Metoden returnerer en trådsikker visning af det medfølgende Kort implementering :

Map<Integer, String> syncMap = Collections.synchronizedMap(new HashMap<>());

5. synchronizedSortedMap() Metode

Der er også en modsvarende implementering af synchronizedMap() metode. Det hedder synchronizedSortedMap() , som vi kan bruge til at skabe et synkroniseret Sorteret kort eksempel:

Map<Integer, String> syncSortedMap = Collections.synchronizedSortedMap(new TreeMap<>());

6. synchronizedSet() Metode

Dernæst, gå videre i denne anmeldelse, har vi synchronizedSet() metode. Som navnet antyder, giver det os mulighed for at oprette synkroniserede sæt med minimalt besvær.

Wrapperen returnerer en trådsikker samling understøttet af det angivne Set :

Set<Integer> syncSet = Collections.synchronizedSet(new HashSet<>());

7. synchronizedSortedSet() Metode

Endelig er den sidste synkroniseringsindpakning, som vi vil vise her, synchronizedSortedSet() .

I lighed med andre indpakningsimplementeringer, som vi har gennemgået indtil videre, returnerer metoden en trådsikker version af det givne SortedSet :

SortedSet<Integer> syncSortedSet = Collections.synchronizedSortedSet(new TreeSet<>());

8. Synkroniserede vs samtidige indsamlinger

Indtil dette tidspunkt har vi kigget nærmere på samlingsrammernes synkroniseringsindpakninger.

Lad os nu fokusere på forskellene mellem synkroniserede samlinger og samtidige samlinger , såsom ConcurrentHashMap og BlockingQueue implementeringer.

8.1. Synkroniserede samlinger

Synkroniserede samlinger opnår trådsikkerhed gennem indre låsning, og hele samlingerne er låst . Indre låsning implementeres via synkroniserede blokke inden for den indpakkede samlings metoder.

Som vi kunne forvente, sikrer synkroniserede samlinger datakonsistens/integritet i flertrådede miljøer. De kan dog komme med en bøde i ydeevne, da kun én enkelt tråd kan få adgang til samlingen ad gangen (også kaldet synkroniseret adgang).

For en detaljeret guide til, hvordan du bruger synkroniseret metoder og blokke, tjek venligst vores artikel om emnet.

8.2. Samtidige indsamlinger

Samtidige samlinger (f.eks. ConcurrentHashMap), opnå trådsikkerhed ved at opdele deres data i segmenter . I et ConcurrentHashMap For eksempel kan forskellige tråde få låse på hvert segment, så flere tråde kan få adgang til kortet på samme tid (også kendt som samtidig adgang).

Samtidige samlinger er meget mere effektive end synkroniserede samlinger , på grund af de iboende fordele ved samtidig trådadgang.

Så valget af, hvilken type trådsikker samling, der skal bruges, afhænger af kravene i hver anvendelse, og det bør evalueres i overensstemmelse hermed.

9. Konklusion

I denne artikel tog vi et dybdegående kig på sættet af synkroniseringsindpakninger implementeret i Samlingerne klasse .

Derudover fremhævede vi forskellene mellem synkroniserede og samtidige indsamlinger, og vi så også på de tilgange, de implementerer for at opnå trådsikkerhed.

Som sædvanlig er alle kodeeksemplerne vist i denne artikel tilgængelige på GitHub.


Java tag