Introduktion til Java Memory Model

Java Memory Model (JMM) beskriver opførslen af ​​tråde i Java-runtime-miljøet. Hukommelsesmodellen er en del af Java-sprogets semantik og beskriver, hvad en programmør kan og ikke bør forvente, når han udvikler software ikke til en specifik Java-maskine, men for Java som helhed.

Den originale Java-hukommelsesmodel (som især refererer til "perkolokal hukommelse"), udviklet i 1995, betragtes som en fiasko: mange optimeringer kan ikke foretages uden at miste garantien for kodesikkerhed. Især er der flere muligheder for at skrive multi-threaded "enkelt":

  • enten vil enhver handling med at få adgang til en singleton (selv når objektet blev oprettet for lang tid siden, og intet kan ændre sig) forårsage en inter-thread lås;
  • eller under et bestemt sæt omstændigheder vil systemet udstede en ufærdig enspænder;
  • eller under et bestemt sæt omstændigheder vil systemet skabe to enspændere;
  • eller designet vil afhænge af opførselen af ​​en bestemt maskine.

Derfor er hukommelsesmekanismen blevet redesignet. I 2005, med udgivelsen af ​​Java 5, blev en ny tilgang præsenteret, som blev yderligere forbedret med udgivelsen af ​​Java 14.

Den nye model er baseret på tre regler:

Regel #1 : Enkeltrådede programmer kører pseudo-sekventielt. Dette betyder: i virkeligheden kan processoren udføre flere operationer pr. ur, samtidig med at den ændrer deres rækkefølge, dog forbliver alle dataafhængigheder, så adfærden adskiller sig ikke fra sekventiel.

Regel nummer 2 : der er ingen ud af ingenting værdier. Læsning af en hvilken som helst variabel (undtagen ikke-flygtig lang og dobbelt, for hvilke denne regel muligvis ikke gælder) vil returnere enten standardværdien (nul) eller noget skrevet der af en anden kommando.

Og regel nummer 3 : resten af ​​begivenhederne udføres i rækkefølge, hvis de er forbundet med et strengt delordreforhold "eksekverer før" ( sker før ).

Sker før

Leslie Lamport kom op med konceptet Happens før . Dette er en streng delordensrelation introduceret mellem atomiske kommandoer (++ og -- er ikke atomare) og betyder ikke "fysisk før".

Den siger, at det andet hold vil være "vidende" om ændringerne foretaget af det første.

Sker før

For eksempel udføres den ene før den anden for sådanne operationer:

Synkronisering og skærme:

  • Indfanger skærmen ( låsemetode , synkroniseret start) og hvad der sker i den samme tråd efter den.
  • Retur af skærmen (metode oplåsning , slutningen af ​​synkroniseret) og hvad der sker på samme tråd før det.
  • Returnerer skærmen og fanger den derefter af en anden tråd.

At skrive og læse:

  • Skriver til enhver variabel og læser den derefter i samme strøm.
  • Alt i samme tråd før du skriver til den flygtige variabel, og selve skrivningen. flygtig læsning og alt på samme tråd efter det.
  • At skrive til en flygtig variabel og derefter læse den igen. En flygtig skrivning interagerer med hukommelsen på samme måde som en monitorretur, mens en læsning er som en optagelse. Det viser sig, at hvis den ene tråd skrev til en flygtig variabel, og den anden fandt den, bliver alt, der går forud for skrivningen, udført før alt, der kommer efter læsningen; se billede.

Objektvedligeholdelse:

  • Statisk initialisering og enhver handling med alle forekomster af objekter.
  • Skrivning til sidste felter i konstruktøren og alt efter konstruktøren. Som en undtagelse forbinder sker-før-relationen ikke transitivt til andre regler og kan derfor forårsage et løb mellem tråde.
  • Ethvert arbejde med objektet og finalize() .

Stream service:

  • Start af en tråd og eventuel kode i tråden.
  • Nulstilling af variabler relateret til tråden og eventuel kode i tråden.
  • Kode i tråd og join() ; kode i tråden og isAlive() == falsk .
  • interrupt() tråden og registrere, at den er stoppet.

Sker før arbejdsnuancer

Frigivelse af en sker-før-skærm sker, før den samme skærm anskaffes. Det er værd at bemærke, at det er frigivelsen og ikke udgangen, det vil sige, at du ikke behøver at bekymre dig om sikkerheden, når du bruger ventetid.

Lad os se, hvordan denne viden vil hjælpe os med at rette vores eksempel. I dette tilfælde er alt meget enkelt: Fjern bare den eksterne kontrol og lad synkroniseringen være som den er. Nu vil den anden tråd garanteret se alle ændringerne, for den får først skærmen, når den anden tråd har frigivet den. Og da han ikke vil frigive det, før alt er initialiseret, vil vi se alle ændringerne på én gang og ikke separat:

public class Keeper {
    private Data data = null;

    public Data getData() {
        synchronized(this) {
            if(data == null) {
                data = new Data();
            }
        }

        return data;
    }
}

At skrive til en flygtig variabel sker - før læsning fra den samme variabel. Ændringen, vi har lavet, retter selvfølgelig fejlen, men den sætter den, der skrev den originale kode tilbage, hvor den kom fra - blokering hver gang. Det flygtige søgeord kan spare. Faktisk betyder det pågældende udsagn, at når vi læser alt, hvad der er erklæret flygtigt, vil vi altid få den faktiske værdi.

Derudover, som jeg sagde tidligere, for flygtige felter er skrivning altid (inklusive lang og dobbelt) en atomoperation. Et andet vigtigt punkt: hvis du har en flygtig enhed, der har referencer til andre entiteter (for eksempel en matrix, List eller en anden klasse), så vil kun en reference til selve entiteten altid være "frisk", men ikke til alt i det indkommende.

Så tilbage til vores dobbeltlåsende væddere. Ved at bruge volatile kan du løse situationen som denne:

public class Keeper {
    private volatile Data data = null;

    public Data getData() {
        if(data == null) {
            synchronized(this) {
                if(data == null) {
                    data = new Data();
                }
            }
        }
        return data;
    }
}

Her har vi stadig en lås, men kun hvis data == null. Vi filtrerer de resterende tilfælde fra ved hjælp af flygtig læsning. Korrekthed er sikret ved, at volatile store sker-før volatile read, og alle operationer, der forekommer i konstruktøren, er synlige for den, der læser værdien af ​​feltet.