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

hvordan man forstår flygtigt eksempel i Java Language Specification?

Eksemplerne er mere end "lidt forkerte".

For det første har du ret i, at selv uden omarrangering, j kan forekomme større end i i dette eksempel. Dette er endda erkendt senere i samme eksempel:

En anden fremgangsmåde ville være at erklære i og j at være volatile :

class Test {
    static volatile int i = 0, j = 0;
    static void one() { i++; j++; }
    static void two() {
        System.out.println("i=" + i + " j=" + j);
    }
}

Dette tillader metode one og metode two skal udføres samtidigt, men garanterer, at adgang til de delte værdier for i og j forekomme nøjagtigt lige så mange gange og i nøjagtig samme rækkefølge, som de ser ud til at forekomme under udførelsen af ​​programteksten af ​​hver tråd. Derfor er den delte værdi for j er aldrig større end for i , fordi hver opdatering til i skal afspejles i den delte værdi for i før opdateringen til j opstår. Det er dog muligt, at enhver given påkaldelse af metode two kan observere en værdi for j det er meget større end værdien observeret for i , fordi metode one kan blive udført mange gange mellem det tidspunkt, hvor metode two henter værdien i og det øjeblik, hvor metoden two henter værdien j .

Selvfølgelig er det abstrut at sige "den delte værdi for j er aldrig større end for i ”, bare for at sige lige i næste sætning “Det er muligt … [at] observere en værdi for j det er meget større end værdien observeret for i ”.

j er aldrig større end i , undtagen når det observeres at være meget større end i ? Skal det sige, at "lidt større" er umuligt?

Selvfølgelig ikke. Denne udtalelse giver ingen mening og ser ud til at være resultatet af et forsøg på at adskille en eller anden objektiv sandhed som "den delte værdi" fra "den observerede værdi", hvorimod der faktisk kun er observerbar adfærd i et program.

Dette er illustreret med den forkerte sætning:

Dette gør det muligt at udføre metode et og metode to samtidigt, men garanterer adgang til de delte værdier for i og j forekomme nøjagtigt lige så mange gange og i nøjagtig samme rækkefølge, som de ser ud til at forekomme under udførelse af programteksten af ​​hver tråd.

Selv med volatile variable, er der ingen sådan garanti. Alt, som JVM skal garantere, er, at den observerede adfærd modsiger ikke specifikationen, så når du påberåber dig one() tusind gange i en sløjfe, for eksempel, kan en optimeringsmaskine stadig erstatte den med en atomær stigning med tusinde, hvis det kan udelukke muligheden for, at en anden tråd er vidne til tilstedeværelsen af ​​en sådan optimering (bortset fra at udlede af den højere hastighed).

Eller med andre ord, hvor mange gange en variabel (hhv. dens hukommelsesplacering) faktisk tilgås, er ikke observerbar og derfor ikke specificeret. Det gør alligevel ikke noget. Det eneste, der betyder noget for en applikationsprogrammør, er j kan være større end i , om variablerne er erklæret volatile eller ej.

Skifter rækkefølgen af ​​læsninger af i og j inden for two() kunne gøre det til et bedre eksempel, men jeg tror, ​​det ville være bedst, hvis JLS §8.3.1.2 ikke forsøgte at forklare betydningen af ​​volatile i daglig tale, men sagde netop, at den pålægger speciel semantik i henhold til hukommelsesmodellen og overlod det til JMM at forklare det på en formelt korrekt måde.

Programmører formodes ikke at mestre samtidighed blot ved at læse 8.3.1.4., så eksemplet er meningsløst her (i bedste tilfælde; det værste tilfælde ville være at skabe det indtryk, at dette eksempel var tilstrækkeligt til at forstå sagen).


Det Holger siger i sit svar er helt korrekt (læs det igen og accepter det), vil jeg bare tilføje, at ved at bruge jcstress, er dette endda let at bevise. Selve testen er kun en mindre refaktor fra kohærensprøven (som er superbe! IMO):

import org.openjdk.jcstress.annotations.Actor;
import org.openjdk.jcstress.annotations.Expect;
import org.openjdk.jcstress.annotations.JCStressTest;
import org.openjdk.jcstress.annotations.Outcome;
import org.openjdk.jcstress.annotations.State;
import org.openjdk.jcstress.infra.results.II_Result;

@JCStressTest
@Outcome(id = "0, 1", expect = Expect.ACCEPTABLE_INTERESTING, desc = "only j updated")
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "only i updated")
@Outcome(id = "0, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "both updates lost")
@Outcome(id = "1, 1", expect = Expect.ACCEPTABLE, desc = "both updated")
@State
public class SOExample {

    private final Holder h1 = new Holder();
    private final Holder h2 = h1;

    @Actor
    public void writeActor() {
        ++h1.i;
        ++h1.j;

    }

    @Actor
    public void readActor(II_Result result) {
        Holder h1 = this.h1;
        Holder h2 = this.h2;

        h1.trap = 0;
        h2.trap = 0;

        result.r1 = h1.i;
        result.r2 = h2.j;
    }

    static class Holder {

        int i = 0;
        int j = 0;

        int trap;
    }

}

Selvom du ikke forstår koden, er pointen, at kørsel af den vil vise ACCEPTABLE_INTERESTING som absolut mulige resultater; være det med volatile int i = 0; volatile int j = 0; eller uden den volatile .


Java tag