Java-Multithreading-Grundlagen | JUC-Grundlagen der gleichzeitigen Programmierung

1. Grundkonzepte von Prozessen und Threads

Am Anfang existieren keine Prozesse und Threads, sondern Konzepte, die bei Bedarf auftauchen.

1. Der Hintergrund des Prozesses

Zunächst konnte der Computer nur bestimmte Anweisungen akzeptieren. Wenn der Benutzer eine Anweisung eingibt, führt der Computer eine Operation aus. Die Geschwindigkeit der Benutzereingabe ist jedoch weitaus geringer als die Geschwindigkeit der Computerberechnung, sodass der Computer viel Zeit damit verbringt, auf Benutzereingaben zu warten, dh die CPU befindet sich immer im Leerlauf und die Auslastungsrate der CPU ist gleich sehr niedrig.

Batch-Betriebssystem

Später gab es ein Batch-Betriebssystem, das eine Reihe von Anweisungen in eine Liste schrieb und die Liste auf einmal an den Computer weitergab. Anschließend las der Computer die Anweisungen Zeile für Zeile und gab das Ergebnis auf einer anderen Festplatte aus.

Obwohl das Aufkommen des Batch-Betriebssystems die Effizienz des Computers verbessert hat, läuft immer nur ein Programm im Speicher, da der Befehlsbetriebsmodus des Batch-Betriebssystems immer noch seriell ist . Diese Art von Stapelverarbeitungsbetriebssystem ist nicht ideal, da die CPU gleichzeitig nur die Anweisungen eines Programms ausführen kann, dh nur ein Programm im Speicher ausgeführt werden kann.

Vorschlag zum Prozess

Da im Speicher nur ein Programm ablaufen kann, entwickelten Informatiker das Konzept eines Prozesses.

Ein Prozess ist der von einem Anwendungsprogramm im Speicher zugewiesene Speicherplatz, dh mehrere Programme können gleichzeitig im Speicher ausgeführt werden , und jedes Anwendungsprogramm (jeder Prozess) stört sich nicht gegenseitig. Jeder Prozess speichert den Status des laufenden Programms.

Programm: Bezieht sich auf eine Sammlung von Codes, die bestimmte Funktionen ausführen können.

Die CPU eines Computers führt jeden Prozess in Zeitabschnitten aus. Die CPU weist jedem Prozess eine Zeitscheibe zu. Wenn der Prozess am Ende der Zeitscheibe noch läuft, wird der Prozess angehalten und die CPU einem anderen Prozess zugewiesen (dieser Prozess ist eine Kontextumschaltung). Wenn der Prozess vor Ablauf des Zeitquantums blockiert oder beendet wird, schaltet die CPU sofort um, ohne den Ablauf des Zeitquantums abzuwarten.

Es ist zu beachten, dass das Umschalten des CPU-Kontexts ein sehr zeitaufwändiger Vorgang ist, da der Status des aktuellen Prozesses (Prozessidentifikation, vom Prozess verwendete Ressourcen usw.) vor dem Umschalten des Prozesses gespeichert werden muss, damit dies praktisch ist um beim nächsten Mal die CPU-Zeitscheibe entsprechend der zuvor gespeicherten zu erhalten. Der Status wird wiederhergestellt und die Ausführung wird fortgesetzt.

Bei Verwendung der Methode „CPU-Zeitscheibe + Prozess“ hat man makroskopisch den Eindruck, dass mehrere Aufgaben im gleichen Zeitraum ausgeführt werden. Dies liegt daran, dass die Berechnungsgeschwindigkeit der CPU zu hoch ist, sodass es wie gleichzeitige Verarbeitungsaufgaben aussieht. Tatsächlich wird bei einer Single-Core-CPU jeweils nur ein Programm ausgeführt.

Parallelität: Mehrere Aufgaben gleichzeitig bearbeiten.

Parallelität: Mehrere Aufgaben gleichzeitig bearbeiten.

Weitere Erhöhung der Betriebssystemanforderungen

Obwohl das Erscheinungsbild des Prozesses die Leistung des Betriebssystems im Laufe der Zeit erheblich verbessert hat, sind die Menschen nicht zufrieden damit, dass ein Prozess in einem bestimmten Zeitraum nur eine Aufgabe ausführen kann. Wenn ein Prozess mehrere Unteraufgaben hat, können diese nur ausgeführt werden Diese Teilaufgaben werden einzeln ausgeführt. Diese Teilaufgaben wirken sich stark auf die Effizienz aus.

Zum Beispiel: Mit der Antivirensoftware auf unserem Computer können wir nicht gleichzeitig nach Viren suchen, sondern auch nach Junk-E-Mails suchen. Wir müssen warten, bis der Virenscan abgeschlossen ist, bevor wir nach Junk-E-Mails suchen können.

Thread-Präsentation

Können wir diese Teilaufgaben gleichzeitig ausführen lassen? Deshalb wurde das Konzept des Threads vorgeschlagen. Ein Thread ist eine Einheit, die kleiner ist als ein Prozess. Ein Programm ist ein Prozess, und ein Prozess kann einen oder mehrere Threads enthalten.

Bild-20230405200232690

Beispiel: Die Antivirensoftware auf unserem Computer kann gleichzeitig nach Viren und Müll suchen.

Prozesse ermöglichen die Parallelität des Betriebssystems und Threads ermöglichen die interne Parallelität von Prozessen.

Warum müssen wir Prozesse vorschlagen, da Prozesse auch Parallelität erreichen können?

  • Die Prozesskommunikation ist komplizierter, Daten lassen sich nicht einfach teilen und es gibt Speicherbarrieren zwischen verschiedenen Prozessen. Der Thread erleichtert den Datenaustausch und die Kommunikation zwischen Threads.
  • Prozesse sind schwergewichtig und der Overhead von Switching-Prozessen ist relativ hoch. Es müssen nicht nur Register und Stapelinformationen gespeichert werden, sondern auch Ressourcenzuweisung und -recycling sowie Paging erforderlich sein. Der Thread muss nur Register und Stapelinformationen speichern, und der Overhead ist relativ gering.

Zweitens: Multithreading

Oben haben wir über die Gründe für Prozesse und Threads gesprochen. Wie erstellt man Threads in Java?

1. Erben Sie die Thread-Klasse

class MyThread extends Thread {
    
    

    @Override
    public void run() {
    
    
        System.out.println(Thread.currentThread().getName());
    }
}
public class Demo04 {
    
    
    public static void main(String[] args) {
    
    
        MyThread thread1 = new MyThread();
        thread1.setName("子线程1");
        thread1.start();
        MyThread thread2 = new MyThread();
        thread2.setName("子线程2");
        thread2.start();
        System.out.println(Thread.currentThread().getName());
    }
}

Hinweis: Derselbe Thread kann start()die Methode nicht mehrmals aufrufen, andernfalls wird IllegalThreadStateExceptioneine Ausnahme .

start()-Quellcode:

private volatile int threadStatus = 0;
public synchronized void start() {
    
    
    /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
    group.add(this);

    boolean started = false;
    try {
    
    
        start0();
        started = true;
    } finally {
    
    
        try {
    
    
            if (!started) {
    
    
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
    
    
            /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
        }
    }
}

private native void start0();

Nach der Analyse des Quellcodes wurde festgestellt , dass sich der Wert ändert, der sich nach threadStatus = 0dem Aufruf der lokalen Methode ändert, sodass beim zweiten Aufruf der Methode eine Ausnahme gemeldet wird .start0()threadStatusstart()IllegalThreadStateException

2. Implementieren Sie die Runnable-Schnittstelle

Threads werden durch die Vererbung der oben genannten Thread-Klasse erstellt. Wir wissen jedoch, dass es sich in Java um eine einzelne Vererbung und mehrere Implementierungen handelt. Wenn eine Klasse die Thread-Klasse erbt, kann sie andere Klassen nicht explizit erben. Wenn wir eine neue Thread-Aufgabe erstellen und andere Klassen erben möchten, wie erreichen wir das?

Im Konstruktor von Thread wird die Erstellung eines Threads durch Übergabe einer Implementierungsklasse der Runnable-Schnittstelle unterstützt.

Bild-20230405203241447

class MyCustomThread implements Runnable {
    
    

    @Override
    public void run() {
    
    
        System.out.println(Thread.currentThread().getName());
    }
}
public class Demo05 {
    
    
    public static void main(String[] args) {
    
    
        Thread thread = new Thread(new MyCustomThread());
        thread.start();
    }
}

Wir können den obigen Code auch vereinfachen, indem wir ihn beispielsweise als anonymes inneres Klassenobjekt oder als Lamdab-Ausdruck schreiben.

Was ist ein anonymes inneres Klassenobjekt?

Eine anonyme innere Klasse bezieht sich auf eine innere Klasse ohne Namen, die bei der Deklaration direkt eine Schnittstelle implementiert oder von einer Klasse erbt und bei Verwendung direkt erstellt und instanziiert wird. In Java werden sehr häufig anonyme innere Klassen verwendet, die das Schreiben von Code vereinfachen, den Code prägnanter machen und es Programmierern auch ermöglichen können, die Implementierungsdetails des Codes bis zu einem gewissen Grad zu verbergen.

Format:

Neuer übergeordneter Klassenkonstruktor <tatsächliche Parameterliste> implementiert Schnittstellennamen <generische Parameterliste> {externe Klassenmitgliedsvariable, Methode; [interne Klassenmitgliedsvariable] [interne Klassenmethode]}

Wenn Sie nach JDK 8 externe Variablen nicht ändern, können Sie ohne endgültige Änderung direkt auf sie zugreifen.

public class OuterClass {
     
     
    public void myMethod() {
     
     
        final int x = 3; // 将x声明为final
        new Thread(new Runnable() {
     
     
            @Override
            public void run() {
     
     
                x++; // 编译错误,无法修改x的值
                System.out.println(x);
            }
        }).start();
    }
}

Anonymes inneres Klassenobjekt:

public class Demo05 {
    
    
    public static void main(String[] args) {
    
    
        Thread thread = new Thread(new Runnable {
    
    
            @Override
            public void run() {
    
    
                System.out.println(Thread.currentThread().getName());
            }
        });
        thread.start();
    }
}

Zur Unterscheidung zwischen anonymen Objekten, anonymen inneren Klassenobjekten und inneren Klassen.

Anonymes Objekt: Dem erstellten Objekt wird kein Name gegeben.

Zum Beispiel: new MyThread().

Innere Klasse: Eine innerhalb einer Klasse definierte Klasse.

Zum Beispiel:

class OutterClass {
     
     
    // 外部类成员
    class InnerClass {
     
     
        // 内部类成员
    }
}

Anonymes inneres Klassenobjekt: Der Instanz der inneren Klasse wird kein Name gegeben.

Wir können den Code weiter vereinfachen und ihn als Lamdab-Ausdruck schreiben.

public class Demo05 {
    
    
    public static void main(String[] args) {
    
    
        new Thread(() -> System.out.println(Thread.currentThread().getName())).start();
    }
}

Wenn Sie mit Lamdab nicht vertraut sind, können Sie online suchen und lernen.

Um Lamdab verwenden zu können, muss die verwendete JDK-Version größer oder gleich 8 sein.


Weitere Inhalte sind auf meinem Blog willkommen .

Ich denke du magst

Origin blog.csdn.net/qq_43907505/article/details/130054237
Empfohlen
Rangfolge