CodeGym/Java blog/Tilfældig/Bedre sammen: Java og Tråd-klassen. Del III — Interaktion...
John Squirrels
Niveau
San Francisco

Bedre sammen: Java og Tråd-klassen. Del III — Interaktion

Udgivet i gruppen
En kort oversigt over detaljerne i, hvordan tråde interagerer. Tidligere har vi set på, hvordan tråde er synkroniseret med hinanden. Denne gang vil vi dykke ned i de problemer, der kan opstå, når tråde interagerer, og vi vil tale om, hvordan man undgår dem. Vi vil også give nogle nyttige links til mere dybdegående undersøgelse. Bedre sammen: Java og Tråd-klassen.  Del III — Interaktion - 1

Introduktion

Så vi ved, at Java har tråde. Det kan du læse om i anmeldelsen med titlen Better together: Java and the Thread class. Del I — Udførelsestråde . Og vi undersøgte det faktum, at tråde kan synkroniseres med hinanden i anmeldelsen med titlen Better together: Java and the Thread class. Del II — Synkronisering . Det er tid til at tale om, hvordan tråde interagerer med hinanden. Hvordan deler de delte ressourcer? Hvilke problemer kan der opstå her? Bedre sammen: Java og Tråd-klassen.  Del III — Interaktion - 2

dødvande

Det mest skræmmende problem af alle er dødvande. Deadlock er, når to eller flere tråde evigt venter på den anden. Vi tager et eksempel fra Oracle-websiden, der beskriver dødvande :
public class Deadlock {
    static class Friend {
        private final String name;
        public Friend(String name) {
            this.name = name;
        }
        public String getName() {
            return this.name;
        }
        public synchronized void bow(Friend bower) {
            System.out.format("%s: %s bowed to me!%n",
                    this.name, bower.getName());
            bower.bowBack(this);
        }
        public synchronized void bowBack(Friend bower) {
            System.out.format("%s: %s bowed back to me!%n",
                    this.name, bower.getName());
        }
    }

    public static void main(String[] args) {
        final Friend alphonse = new Friend("Alphonse");
        final Friend gaston = new Friend("Gaston");
        new Thread(() -> alphonse.bow(gaston)).start();
        new Thread(() -> gaston.bow(alphonse)).start();
    }
}
Deadlock forekommer muligvis ikke her første gang, men hvis dit program hænger, så er det tid til at køre jvisualvm: Bedre sammen: Java og Tråd-klassen.  Del III — Interaktion - 3Med et JVisualVM plugin installeret (via Værktøjer -> Plugins), kan vi se, hvor dødlåsen opstod:
"Thread-1" - Thread t@12
   java.lang.Thread.State: BLOCKED
	at Deadlock$Friend.bowBack(Deadlock.java:16)
	- waiting to lock <33a78231> (a Deadlock$Friend) owned by "Thread-0" t@11
Tråd 1 venter på låsen fra tråd 0. Hvorfor sker det? Thread-1begynder at køre og udfører Friend#bowmetoden. Det er markeret med nøgleordet synchronized, hvilket betyder, at vi anskaffer monitoren til this(det aktuelle objekt). Metodens input var en reference til det andet Friendobjekt. Nu Thread-1ønsker at udføre metoden på den anden Friend, og skal erhverve sin lås for at gøre det. Men hvis den anden tråd (i dette tilfælde Thread-0) formåede at komme ind i bow()metoden, så er låsen allerede blevet erhvervet og Thread-1venter påThread-0, og omvendt. Dette dødvande er uløseligt, og vi kalder det dødvande. Som et dødsgreb, der ikke kan slippes, er dødvande gensidig blokering, der ikke kan brydes. For en anden forklaring på deadlock, kan du se denne video: Deadlock og Livelock Explained .

Livelock

Hvis der er dødvande, er der så også livelock? Ja, det er der :) Livelock opstår, når tråde udadtil ser ud til at være i live, men de er ude af stand til at gøre noget, fordi de(n) betingelser, der kræves for, at de kan fortsætte deres arbejde, ikke kan opfyldes. Dybest set ligner livelock deadlock, men trådene "hænger" ikke og venter på en skærm. I stedet gør de for altid noget. For eksempel:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class App {
    public static final String ANSI_BLUE = "\u001B[34m";
    public static final String ANSI_PURPLE = "\u001B[35m";

    public static void log(String text) {
        String name = Thread.currentThread().getName(); // Like "Thread-1" or "Thread-0"
        String color = ANSI_BLUE;
        int val = Integer.valueOf(name.substring(name.lastIndexOf("-") + 1)) + 1;
        if (val != 0) {
            color = ANSI_PURPLE;
        }
        System.out.println(color + name + ": " + text + color);
        try {
            System.out.println(color + name + ": wait for " + val + " sec" + color);
            Thread.currentThread().sleep(val * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Lock first = new ReentrantLock();
        Lock second = new ReentrantLock();

        Runnable locker = () -> {
            boolean firstLocked = false;
            boolean secondLocked = false;
            try {
                while (!firstLocked || !secondLocked) {
                    firstLocked = first.tryLock(100, TimeUnit.MILLISECONDS);
                    log("First Locked: " + firstLocked);
                    secondLocked = second.tryLock(100, TimeUnit.MILLISECONDS);
                    log("Second Locked: " + secondLocked);
                }
                first.unlock();
                second.unlock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        new Thread(locker).start();
        new Thread(locker).start();
    }
}
Succesen med denne kode afhænger af den rækkefølge, som Java-trådplanlæggeren starter trådene i. Hvis Thead-1starter først, så får vi livelock:
Thread-1: First Locked: true
Thread-1: wait for 2 sec
Thread-0: First Locked: false
Thread-0: wait for 1 sec
Thread-0: Second Locked: true
Thread-0: wait for 1 sec
Thread-1: Second Locked: false
Thread-1: wait for 2 sec
Thread-0: First Locked: false
Thread-0: wait for 1 sec
...
Som du kan se fra eksemplet, forsøger begge tråde at erhverve begge låse på skift, men de mislykkes. Men de er ikke i dødvande. Udadtil er alt godt, og de gør deres arbejde. Bedre sammen: Java og Tråd-klassen.  Del III — Interaktion - 4Ifølge JVisualVM ser vi perioder med søvn og en parkperiode (det er, når en tråd forsøger at erhverve en lås - den går ind i parktilstanden, som vi diskuterede tidligere, da vi talte om trådsynkronisering ) . Du kan se et eksempel på livelock her: Java - Thread Livelock .

Sult

Ud over deadlock og livelock er der et andet problem, der kan ske under multithreading: sult. Dette fænomen adskiller sig fra de tidligere former for blokering ved, at trådene ikke er blokeret - de har simpelthen ikke tilstrækkelige ressourcer. Som et resultat, mens nogle tråde tager al udførelsestiden, er andre ikke i stand til at køre: Bedre sammen: Java og Tråd-klassen.  Del III — Interaktion - 5

https://www.logicbig.com/

Du kan se et super eksempel her: Java - Thread Starvation and Fairness . Dette eksempel viser, hvad der sker med tråde under sult, og hvordan en lille ændring fra Thread.sleep()til Thread.wait()lader dig fordele belastningen jævnt. Bedre sammen: Java og Tråd-klassen.  Del III — Interaktion - 6

Race forhold

I multithreading er der sådan noget som en "race condition". Dette fænomen opstår, når tråde deler en ressource, men koden er skrevet på en måde, så den ikke sikrer korrekt deling. Tag et kig på et eksempel:
public class App {
    public static int value = 0;

    public static void main(String[] args) {
        Runnable task = () -> {
            for (int i = 0; i < 10000; i++) {
                int oldValue = value;
                int newValue = ++value;
                if (oldValue + 1 != newValue) {
                    throw new IllegalStateException(oldValue + " + 1 = " + newValue);
                }
            }
        };
        new Thread(task).start();
        new Thread(task).start();
        new Thread(task).start();
    }
}
Denne kode genererer muligvis ikke en fejl første gang. Når det sker, kan det se sådan ud:
Exception in thread "Thread-1" java.lang.IllegalStateException: 7899 + 1 = 7901
	at App.lambda$main$0(App.java:13)
	at java.lang.Thread.run(Thread.java:745)
Som du kan se, gik noget galt, mens newValueder blev tildelt en værdi. newValueer for stor. På grund af racetilstanden lykkedes det en af ​​trådene at ændre variablerne valuemellem de to udsagn. Det viser sig, at der er et løb mellem trådene. Tænk nu på, hvor vigtigt det er ikke at lave lignende fejl med pengetransaktioner... Eksempler og diagrammer kan også ses her: Kode til at simulere racetilstand i Java-tråd .

Flygtig

Når vi taler om interaktionen mellem tråde, volatileer nøgleordet værd at nævne. Lad os se på et simpelt eksempel:
public class App {
    public static boolean flag = false;

    public static void main(String[] args) throws InterruptedException {
        Runnable whileFlagFalse = () -> {
            while(!flag) {
            }
            System.out.println("Flag is now TRUE");
        };

        new Thread(whileFlagFalse).start();
        Thread.sleep(1000);
        flag = true;
    }
}
Mest interessant er det, at dette med stor sandsynlighed ikke virker. Den nye tråd vil ikke se ændringen i flagfeltet. For at rette dette for flagfeltet skal vi bruge søgeordet volatile. Hvordan og hvorfor? Processoren udfører alle handlingerne. Men resultaterne af beregninger skal gemmes et sted. Til dette er der hovedhukommelse og der er processorens cache. En processors caches er som en lille del hukommelse, der bruges til at få adgang til data hurtigere, end når man får adgang til hovedhukommelsen. Men alt har en ulempe: dataene i cachen er muligvis ikke opdaterede (som i eksemplet ovenfor, når værdien af ​​flagfeltet ikke blev opdateret). Såvolatilenøgleord fortæller JVM, at vi ikke ønsker at cache vores variabel. Dette gør det muligt at se det opdaterede resultat på alle tråde. Dette er en meget forenklet forklaring. Hvad angår søgeordet volatile, anbefaler jeg stærkt, at du læser denne artikel . For mere information råder jeg dig også til at læse Java Memory Model og Java Volatile Keyword . Derudover er det vigtigt at huske, at det volatilehandler om synlighed og ikke om atomiciteten af ​​ændringer. Ser vi på koden i afsnittet "Racebetingelser", vil vi se et værktøjstip i IntelliJ IDEA: Bedre sammen: Java og Tråd-klassen.  Del III — Interaktion - 7Denne inspektion blev føjet til IntelliJ IDEA som en del af udgave IDEA-61117 , som blev opført i Release Notes tilbage i 2010.

Atomicitet

Atomoperationer er operationer, der ikke kan opdeles. For eksempel skal operationen med at tildele en værdi til en variabel være atomisk. Desværre er inkrementoperationen ikke atomisk, fordi inkrement kræver så mange som tre CPU-operationer: Hent den gamle værdi, føj en til den, og gem derefter værdien. Hvorfor er atomicitet vigtig? Med inkrementoperationen, hvis der er en race-tilstand, kan den delte ressource (dvs. den delte værdi) pludselig ændre sig til enhver tid. Derudover er operationer, der involverer 64-bit strukturer, for eksempel longog double, ikke atomare. Flere detaljer kan læses her: Sørg for atomicitet ved læsning og skrivning af 64-bit værdier . Problemer relateret til atomicitet kan ses i dette eksempel:
public class App {
    public static int value = 0;
    public static AtomicInteger atomic = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            for (int i = 0; i < 10000; i++) {
                value++;
                atomic.incrementAndGet();
            }
        };
        for (int i = 0; i < 3; i++) {
            new Thread(task).start();
        }
        Thread.sleep(300);
        System.out.println(value);
        System.out.println(atomic.get());
    }
}
Specialklassen AtomicIntegervil altid give os 30.000, men det valuevil ændre sig fra tid til anden. Der er en kort oversigt over dette emne: Introduktion til atomvariabler i Java . "Sammenlign-og-byt"-algoritmen ligger i hjertet af atomklasser. Du kan læse mere om det her i Sammenligning af låsefri algoritmer - CAS og FAA på eksemplet med JDK 7 og 8 eller i Sammenlign-og-byt- artiklen på Wikipedia. Bedre sammen: Java og Tråd-klassen.  Del III — Interaktion - 9

http://jeremymanson.blogspot.com/2008/11/what-volatile-means-in-java.html

Sker-før

Der er et interessant og mystisk koncept, der hedder "skeder før". Som en del af din undersøgelse af tråde, bør du læse om det. Sker-før-forholdet viser rækkefølgen, hvori handlinger mellem tråde vil blive set. Der er mange fortolkninger og kommentarer. Her er en af ​​de seneste præsentationer om dette emne: Java "Happens-Before"-relationer .

Resumé

I denne anmeldelse har vi undersøgt nogle af detaljerne i, hvordan tråde interagerer. Vi diskuterede problemer, der kan opstå, samt måder at identificere og eliminere dem på. Liste over yderligere materialer om emnet: Bedre sammen: Java og Tråd-klassen. Del I — Udførelsestråde Bedre sammen: Java og trådklassen. Del II — Synkronisering Bedre sammen: Java og Thread-klassen. Del IV — Callable, Future og friends Bedre sammen: Java og Thread-klassen. Del V — Executor, ThreadPool, Fork/Join Better sammen: Java og Thread-klassen. Del VI - Fyr væk!
Kommentarer
  • Populær
  • Ny
  • Gammel
Du skal være logget ind for at skrive en kommentar
Denne side har ingen kommentarer endnu