Java >> Java tutorial >  >> Tag >> volatile

Flygtige variable og gevindsikkerhed

1. Oversigt

Mens den flygtige søgeord i Java sikrer normalt trådsikkerhed, det er ikke altid tilfældet.

I denne tutorial ser vi på scenariet, når en delt flygtig variabel kan føre til en racetilstand.

2. Hvad er en flygtig Variabel?

I modsætning til andre variabler, flygtig variabler skrives til og læses fra hovedhukommelsen. CPU'en cacher ikke værdien af ​​en flygtig variabel.

Lad os se, hvordan man erklærer en flygtig variabel:

static volatile int count = 0;

3. Egenskaber for volatile Variabler

I dette afsnit vil vi se på nogle vigtige funktioner ved flygtig variabler.

3.1. Synlighedsgaranti

Antag, at vi har to tråde, der kører på forskellige CPU'er, og som har adgang til en delt, ikke-flygtig variabel. Lad os yderligere antage, at den første tråd skriver til en variabel, mens den anden tråd læser den samme variabel.

Hver tråd kopierer værdien af ​​variablen fra hovedhukommelsen til dens respektive CPU-cache af ydeevnemæssige årsager.

I tilfælde af ikke-flygtige variabler, garanterer JVM ikke, hvornår værdien vil blive skrevet tilbage til hovedhukommelsen fra cachen.

Hvis den opdaterede værdi fra den første tråd ikke straks skylles tilbage til hovedhukommelsen, er der mulighed for, at den anden tråd kan ende med at læse den ældre værdi.

Diagrammet nedenfor viser ovenstående scenarie:

Her har den første tråd opdateret værdien af ​​variablen count til 5. Men tilbageskylningen af ​​den opdaterede værdi til hovedhukommelsen sker ikke øjeblikkeligt. Derfor læser den anden tråd den ældre værdi. Dette kan føre til forkerte resultater i et multi-threaded miljø.

På den anden side, hvis vi erklærer tæller som flygtig , ser hver tråd sin seneste opdaterede værdi i hovedhukommelsen uden nogen forsinkelse .

Dette kaldes synlighedsgarantien for den flygtige søgeord. Det hjælper med at undgå ovenstående datainkonsekvensproblem.

3.2. Happens-Before-garanti

JVM'en og CPU'en omorganiserer nogle gange uafhængige instruktioner og udfører dem parallelt for at forbedre ydeevnen.

Lad os for eksempel se på to instruktioner, der er uafhængige og kan køre samtidigt:

a = b + c;
d = d + 1;

Men nogle instruktioner kan ikke udføres parallelt, fordi en sidstnævnte instruktion afhænger af resultatet af en tidligere instruktion :

a = b + c;
d = a + e;

Derudover kan genbestilling af selvstændige instruktioner også finde sted. Dette kan forårsage ukorrekt adfærd i en flertrådsapplikation.

Antag, at vi har to tråde med adgang til to forskellige variabler:

int num = 10;
boolean flag = false;

Lad os desuden antage, at den første tråd øger værdien af ​​num og derefter indstille flag  til sand , mens den anden tråd venter indtil flaget  er indstillet til true . Og en gang værdien af ​​flag  er indstillet til true , den anden tråd læser værdien af ​​num.

Derfor bør den første tråd udføre instruktionerne i følgende rækkefølge:

num = num + 10;
flag = true;

Men lad os antage, at CPU'en omarrangerer instruktionerne som:

flag = true;
num = num + 10;

I dette tilfælde, så snart flaget er sat til true , vil den anden tråd begynde at køre. Og fordi variablen num  er endnu ikke opdateret, vil den anden tråd læse den gamle værdi af num , hvilket er 10. Dette fører til forkerte resultater.

Men hvis vi erklærer flag som flygtig , ville den ovennævnte instruktionsgenbestilling ikke være sket.

Anvendelse af flygtige søgeord på en variabel forhindrer instruktionsomlægning ved at give sker-før-garantien.

Dette sikrer, at alle instruktioner før skrivning af flygtige variable er garanteret ikke omarrangeret til at opstå efter den. På samme måde, instruktionerne efter læsning af flygtige variabel kan ikke omarrangeres til at forekomme før den.

4. Hvornår er den flygtige Giver søgeord trådsikkerhed?

Det flygtige søgeord er nyttigt i to scenarier med flere tråde:

  • Når kun én tråd skriver til den flygtige variabel og andre tråde læse dens værdi. Læsetrådene ser således den seneste værdi af variablen.
  • Når flere tråde skriver til en delt variabel, således at operationen er atomisk. Det betyder, at den nye værdi, der skrives, ikke afhænger af den tidligere værdi.

5. Hvornår flygtig Giver du ikke trådsikkerhed?

Det flygtige søgeord er en letvægts synkroniseringsmekanisme.

I modsætning til synkroniseret metoder eller blokke, får det ikke andre tråde til at vente, mens en tråd arbejder på et kritisk afsnit. Derfor er den flygtige søgeord giver ikke trådsikkerhed når ikke-atomare operationer eller sammensatte operationer udføres på delte variable .

Operationer som inkrementering og decrement er sammensatte operationer. Disse operationer involverer internt tre trin:at læse værdien af ​​variablen, opdatere den og derefter skrive den opdaterede værdi tilbage til hukommelsen.

Det korte tidsrum mellem at læse værdien og skrive den nye værdi tilbage til hukommelsen kan skabe en racetilstand. Andre tråde, der arbejder på den samme variabel, kan læse og fungere på den ældre værdi i løbet af det tidsrum.

Desuden, hvis flere tråde udfører ikke-atomare operationer på den samme delte variabel, kan de overskrive hinandens resultater.

I sådanne situationer hvor tråde først skal læse værdien af ​​den delte variabel for at finde ud af den næste værdi, erklærer variablen som flygtig vil ikke virke .

6. Eksempel

Nu vil vi prøve at forstå ovenstående scenarie, når vi erklærer en variabel som flygtig er ikke trådsikker ved hjælp af et eksempel.

Til dette vil vi erklære en delt flygtig variabel med navnet count og initialisere den til nul. Vi vil også definere en metode til at øge denne variabel:

static volatile int count = 0;

void increment() {
    count++;
}

Dernæst opretter vi to tråde t1 og t2. Disse tråde kalder ovenstående inkrementoperation tusind gange:

Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        for(int index=0; index<1000; index++) {
            increment();
        }
    }
});

Thread t2 = new Thread(new Runnable() {
    @Override
    public void run() {
        for(int index=0; index<1000; index++) {
            increment();
        }
    }
});

t1.start();
t2.start();

t1.join();
t2.join();

Fra ovenstående program kan vi forvente, at den endelige værdi af tæller variabel vil være 2000. Men hver gang vi udfører programmet, vil resultatet være anderledes. Nogle gange vil den udskrive den "korrekte" værdi (2000), og nogle gange vil den ikke.

Lad os se på to forskellige output, som vi fik, da vi kørte prøveprogrammet:

value of counter variable: 2000 value of counter variable: 1652

Ovenstående uforudsigelige adfærd skyldes, at begge tråde udfører inkrementeringsoperationen på den delte tælling  variabel . Som tidligere nævnt er tilvækstoperationen ikke atomær . Den udfører tre operationer - læs, opdater og skriv derefter den nye værdi af variablen til hovedhukommelsen. Der er således en stor chance for, at interleaving af disse operationer vil forekomme, når både t1 og t2 kører samtidigt.

Lad os antage t1 og t2  kører samtidigt og t1  udfører stigningsoperationen på optællingen variabel. Men før den skriver den opdaterede værdi tilbage til hovedhukommelsen, tråd t2 læser værdien af ​​tæller variabel fra hovedhukommelsen. I dette tilfælde t2 vil læse en ældre værdi og udføre stigningsoperationen på den samme. Dette kan føre til en forkert værdi af optællingen variabel, der opdateres til hovedhukommelsen . Dermed bliver resultatet et andet end forventet – 2000.

7. Konklusion

I denne artikel så vi, at erklære en delt variabel som flygtig vil ikke altid være trådsikker.

Vi lærte, at for at give trådsikkerhed og undgå løbsforhold for ikke-atomare operationer ved hjælp af synkroniseret metoder eller blokke eller atomare variable er begge levedygtige løsninger.

Som sædvanlig er den komplette kildekode til ovenstående eksempel tilgængelig på GitHub.


No
Java tag