【JavaEE】Multithreading (1)

 

Inhaltsverzeichnis

1. Thread verstehen

1) Was ist ein Thread?

2) Warum brauchen wir Threads? 

3) Der Unterschied zwischen Prozessen und Threads

2. Das erste Multithread-Programm

3. Andere Möglichkeiten zum Erstellen von Multithreads

Methode 2: Implementieren Sie die Runnable-Schnittstelle

Methode 3: Anonyme innere Klasse

 Methode 4: Runable implementieren, Run überschreiben, anonyme innere Klasse

Methode 5: Lambda-Ausdruck verwenden (häufig verwendete Schreibmethode)

2. Thread-Klasse und allgemeine Methoden

2.1 Gemeinsame Konstruktionsmethoden von Thread

2.2 Mehrere gemeinsame Attribute von Thread

Über Vordergrundprozesse und Hintergrundprozesse:

Verwenden Sie setDaemon(true), um den Prozess als Hintergrundprozess festzulegen

Die Funktion von isAlive()

2.3 Starten Sie einen Thread - start()

Interviewfrage: Was ist der Unterschied zwischen Start und Run?

2.4 Einen Thread unterbrechen

2.5 Auf einen Thread warten - join()

2.6 Aktuelle Thread-Referenz abrufen

2.7 Den aktuellen Thread in den Ruhezustand versetzen 

2.8 Vorteile der durch Multithreading erhöhten Laufgeschwindigkeit

3. Thread-Status

3.1 Beobachten Sie alle Zustände von Threads


1. Thread verstehen

1) Was ist ein Thread?

Ein Thread ist ein „Ausführungsfluss“. Jeder Thread kann seinen eigenen Code nacheinander ausführen. Mehrere Threads führen mehrere Codes „gleichzeitig“ aus.

2) Warum brauchen wir Threads? 

Erstens ist die „gleichzeitige Programmierung“ zu einem „rigorosen Bedarf“ geworden.

• Bei der Entwicklung von Single-Core-CPUs ist ein Engpass aufgetreten. Um die Rechenleistung zu verbessern, werden Multi-Core-CPUs benötigt. Durch die gleichzeitige Programmierung können die Multi-Core-CPU-Ressourcen besser genutzt werden.

• Einige Aufgabenszenarien erfordern „Warten auf IO“. Um die Zeit, die auf IO wartet, für andere Arbeiten zu nutzen, muss auch gleichzeitige Programmierung verwendet werden.

Zweitens: Obwohl mehrere Prozesse auch gleichzeitige Programmierung implementieren können, sind Threads leichter als Prozesse.

• Das Erstellen von Threads ist schneller als das Erstellen von Prozessen.

• Das Zerstören von Threads ist schneller als das Zerstören von Prozessen.

• Das Planen von Threads ist schneller als das Planen von Prozessen.

3) Der Unterschied zwischen Prozessen und Threads

• Prozesse enthalten Threads. Jeder Prozess verfügt über mindestens einen Thread, der der Haupt-Thread ist.

• Der Speicherplatz wird nicht von Prozessen gemeinsam genutzt. Threads desselben Prozesses teilen sich denselben Speicherplatz.

• Der Prozess ist die kleinste Einheit für die Systemzuweisung von Ressourcen, und der Thread ist die kleinste Einheit für die Systemplanung.

• Wenn ein Prozess aufhängt, hat dies im Allgemeinen keine Auswirkungen auf andere Prozesse. Wenn ein Thread jedoch aufhängt, kann dies andere Threads im selben Prozess mit sich bringen (der gesamte Prozess stürzt ab).

2. Das erste Multithread-Programm

Spüren Sie den Unterschied zwischen Multithread-Programmen und gewöhnlichen Programmen:

• Jeder Thread ist ein unabhängiger Ausführungsfluss

• Mehrere Threads werden „gleichzeitig“ ausgeführt.

package thread;
import static java.lang.Thread.sleep;
class MyThread extends Thread {
    @Override
    public void run() {
        // run 方法就是该线程的入口方法
        while(true) {
            System.out.println("hello thread");
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class ThreadDemo1 {
    public static void main(String[] args) throws InterruptedException {
        //根据上面的类,创建出实例
        Thread t = new MyThread();
        // 调用 Thread 的start方法,才会真正调用系统 api , 在系统内核中创建出线程
        t.start();

        while(true) {
            System.out.println("hello main");
            sleep(1000);
        }
    }
}

Im Code oben:

Die Ausführungsmethode stellt den Einstiegspunkt des Threads dar. Jeder Thread führt beim Ausführen eine bestimmte Logik aus.

Nach dem Ausführen des Programms können Sie sehen, dass beide Threads gleichzeitig ausgeführt werden. Der T-Thread gibt die Anweisung „Hallo Tread“ aus, und der Hauptthread gibt die Anweisung „Hallo Haupt“ aus.

Warum „Hallo Haupt“ zuerst gedruckt wird:

3. Andere Möglichkeiten zum Erstellen von Multithreads

Methode 2: Implementieren Sie die Runnable-Schnittstelle

package thread;
class MyThread3 implements Runnable {
    @Override
    public void run() {
        while(true) {
            System.out.println("hello runable");
            try {
                Thread.sleep(1000);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class ThreadDemo3 {
    public static void main(String[] args) {
        Runnable runnable = new MyThread3();
        Thread t = new Thread(runnable);
        t.start();

        while(true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Methode 3: Anonyme innere Klasse

package thread;
public class ThreadDemo4 {
    public static void main(String[] args) {
        Thread t = new Thread() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        t.start();

        while (true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

 Methode 4: Runable implementieren, Run überschreiben, anonyme innere Klasse

package thread;
public class ThreadDemo5 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("hello runable");
                    try {
                        Thread.sleep(1000);
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t.start();

        while(true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Methode 5: Lambda-Ausdruck verwenden (häufig verwendete Schreibmethode)

Diese Schreibweise ist relativ prägnant:

package thread;
public class ThreadDemo6 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
           while(true) {
               System.out.println("hello thread");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        t.start();

        while(true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2. Thread-Klasse und allgemeine Methoden

Die Thread-Klasse ist eine Klasse, die von der JVM zum Verwalten von Threads verwendet wird. Mit anderen Worten, jedem Thread ist ein eindeutiges Thread-Objekt zugeordnet. In unserem obigen Beispiel muss jeder Ausführungsfluss auch durch ein Objekt beschrieben werden, ähnlich wie in der folgenden Abbildung dargestellt, und Objekte der Thread-Klasse werden zur Beschreibung eines Thread-Ausführungsflusses verwendet. Die JVM organisiert diese Thread-Objekte für Thread-Planung und Thread-Management.

2.1 Gemeinsame Konstruktionsmethoden von Thread

 Für zwei der Methoden:

Beispiel:

package thread;
public class ThreadDemo7 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"这是我的线程");
        t.start();
    }
}

Verwenden Sie nach dem Ausführen jconsole zur Überwachung, damit Sie die von uns ausgeführten Threads einfacher anzeigen können:

2.2 Mehrere gemeinsame Attribute von Thread

• ID ist die eindeutige Kennung eines Threads und wird nicht von verschiedenen Threads wiederholt.

 • Der Name wird von verschiedenen Debugging-Tools verwendet

• Der Status zeigt die aktuelle Situation des Threads an, die wir weiter unten näher erläutern.

• Threads mit hoher Priorität sind theoretisch einfacher zu planen

• Bei Hintergrund-Threads müssen Sie eines beachten: Die JVM wird nicht beendet, bis alle Nicht-Hintergrund-Threads eines Prozesses beendet sind.

• Ob es aktiv ist oder nicht, das heißt einfach zu verstehen, ist, ob die Ausführung der Ausführungsmethode abgeschlossen ist.

• Thread-Unterbrechungsproblem, wir werden es weiter unten näher erläutern

Über Vordergrundprozesse und Hintergrundprozesse:

Der von unserem Code erstellte Thread ist standardmäßig der Vordergrund-Thread, wodurch verhindert wird, dass der Prozess beendet wird. Solange die Ausführung des Vordergrund-Threads nicht abgeschlossen ist, wird der Prozess nicht beendet. Auch wenn die Ausführung von main abgeschlossen ist.

package thread;
public class ThreadDemo7 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000); 
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"这是我的线程");
        t.start();
        System.out.println("main 执行完毕");
    }
}

Verwenden Sie setDaemon(true) , um den Prozess als Hintergrundprozess festzulegen

Wird auf „true“ gesetzt, um einen Hintergrundprozess anzuzeigen. Der Hintergrund verhindert nicht, dass der Prozess beendet wird.

Wenn es nicht auf „true“ gesetzt ist, ist es der Vordergrund. Der Vordergrund verhindert, dass der Prozess beendet wird. 

package thread;
public class ThreadDemo7 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"这是我的线程");
        //设置为后台进程
        t.setDaemon(true);
        t.start();
        System.out.println("main 执行完毕");
    }
}

Die Funktion von isAlive()

isAlive () gibt an, ob der Thread (PCB) im Kernel noch vorhanden ist. Obwohl die im Java-Code definierte Instanz des Thread-Objekts (Thread) einen Thread darstellt, ist der Lebenszyklus dieses Objekts selbst nicht mit dem Lebenszyklus der PCB abgeschlossen im Kernel. Das gleiche.

package thread;
public class ThreadDemo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()-> {
            // 这个线程的运行时间大概是1s
            try {
                Thread.sleep(1000);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println("start 之前: " + t.isAlive());
        t.start();
        System.out.println("start 之后: " + t.isAlive());
        Thread.sleep(2000);
        // 2s 之后, 线程 t 已经结束了
        System.out.println("t 结束后: " + t.isAlive());
    }
}

2.3 Starten Sie einen Thread - start()

Wir haben zuvor gesehen, wie man ein Thread-Objekt durch Überschreiben der Ausführungsmethode erstellt, aber die Erstellung des Thread-Objekts bedeutet nicht, dass der Thread mit der Ausführung beginnt.

Durch den Aufruf der Startmethode wird tatsächlich ein Thread am unteren Rand des Betriebssystems erstellt.

Der Aufruf von start zum Erstellen eines neuen Threads bedeutet im Wesentlichen, dass start die API des Systems aufruft, um den Thread-Erstellungsvorgang abzuschließen.

Interviewfrage: Was ist der Unterschied zwischen Start und Run?

2.4 Einen Thread unterbrechen

Wie unterbreche ich einen Thread? Derzeit gibt es zwei gängige Methoden:

1. Kommunizieren Sie über gemeinsame Tags

2. Rufen Sie zur Benachrichtigung die Methode interrupt() auf

 Beispiel 1: Benutzerdefinierte Variablen als Flags verwenden.

public class ThreadDemo12 {
    private static boolean isQuit = false;

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while(!isQuit) {
                System.out.println("我是一个线程,工作中!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("线程工作完毕");
        });

        t.start();
        Thread.sleep(3000);
        System.out.println("让 t 线程结束");
        isQuit = true;
    }
}

Hinweis: isQuit kann keine lokale Variable sein. Wenn die Variable hier eine lokale Variable ist, muss sie endgültig oder „Fakt endgültig“ sein. Da isQuit hier geändert werden muss, kann es nicht als endgültig oder „Fakt endgültig“ geschrieben werden können nur als Mitgliedsvariablen geschrieben werden. Warum ist es in Ordnung, Mitgliedsvariablen zu schreiben? Weil Lambda-Ausdrücke im Wesentlichen „funktionale Schnittstellen“ sind – anonyme innere Klassen, und es für innere Klassen in Ordnung ist, auf Mitglieder äußerer Klassen zuzugreifen.

Beispiel 2: Verwenden Sie Thread.interrupted() oder Thread.currentThread().isInterrupted() anstelle von benutzerdefinierten Flags.

public class ThreadDemo13 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while(!Thread.currentThread().isInterrupted()) {
                System.out.println("我是一个线程,工作中!");
                try {
                    Thread.sleep(1000);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                    // 加上 break ,此时抛出异常后,线程也会结束
                    break;
                }
            }
            System.out.println("线程执行完毕");
        });

        t.start();
        Thread.sleep(3000);
        System.out.println("让t线程结束");
        //使用 interrupt 方法,来修改上面"Thread.currentThread().isInterrupted()"的值
        t.interrupt();
    }
}

2.5 Auf einen Thread warten - join()

Manchmal müssen wir warten, bis ein Thread seine Arbeit abgeschlossen hat, bevor wir mit dem nächsten Schritt unserer Arbeit fortfahren können. Beispielsweise kann Zhang San erst nach erfolgreicher Übertragung von Li Si entscheiden, ob Geld gespart werden soll. Zu diesem Zeitpunkt benötigen wir eine Methode, um explizit auf das Ende des Threads zu warten.

public class ThreadDemo14 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
           for(int i = 0; i < 5; i++) {
               System.out.println("我是一个线程,正在工作中...");
               try {
                   Thread.sleep(1000);
               }catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
            System.out.println("线程执行结束");
        });
        t.start();

        t.join();
        System.out.println("这是主线程,期望这个日志在 t 结束后打印");
    }
}

 

Durch den Aufruf von t.join im Hauptthread kann der Hauptthread auf das Ende des t-Threads warten.

Beim Ausführen von Join wird geprüft, ob der T-Thread ausgeführt wird. Wenn T ausgeführt wird, wird der Hauptthread blockiert (der Hauptthread nimmt vorübergehend nicht an der CPU-Ausführung teil).

Wenn t endet, erholt sich der Hauptthread von der Blockierung und setzt die Ausführung fort.

Jeder Thread kann Join aufrufen. Welcher Thread auch immer Join aufruft, dieser Thread wird blockiert und wartet.

Weitere Verbindungsbauweisen:

2.6 Aktuelle Thread-Referenz abrufen

Wenn Sie Thread erben, verwenden Sie dies direkt, um die Thread-Referenz (Thread) abzurufen.

package thread;
class MyThread extends Thread {
    @Override
    public void run() {
        // 这个代码中,如果想要获取到线程的引用,直接使用 this 即可
        System.out.println(this.getId() + ", " + this.getName());
    }
}
public class ThreadDemo16 {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.start();
        t2.start();
    }
}

Wenn es Runnable oder Lambda ist, kann dies nichts tun. Zu diesem Zeitpunkt zeigt dies nicht mehr auf das Thread-Objekt. 

 Sie können nur Thread.currentThread() verwenden

package thread;
public class ThreadDemo17 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            Thread t = Thread.currentThread();
            System.out.println(t.getName());
        });
        Thread t2 = new Thread(() ->{
            Thread t = Thread.currentThread();
            System.out.println(t.getName());
        });
        t1.start();
        t2.start();
    }
}

2.7 Den aktuellen Thread in den Ruhezustand versetzen 

Dies ist auch eine Gruppe von Methoden, mit denen wir besser vertraut sind. Beachten Sie, dass diese Methode nur sicherstellen kann, dass die tatsächliche Ruhezeit größer oder gleich der durch die Parameter festgelegten Ruhezeit ist, da die Thread-Planung nicht kontrollierbar ist.

 

2.8 Vorteile der durch Multithreading erhöhten Laufgeschwindigkeit

Im Folgenden vergleichen wir die Ausführungsgeschwindigkeit zwischen einem einzelnen Thread und mehreren Threads, indem wir 1 bis 10 Milliarden Additionsoperationen ausführen:

Zunächst ein einzelner Thread:

Zwei Threads arbeiten zusammen, um Folgendes zu vervollständigen:

Beachten Sie, dass das Ergebnis hier übergelaufen ist und die Genauigkeit des Ergebnisses nicht berücksichtigt wird, sondern nur die Laufzeit.

3. Thread-Status

3.1 Beobachten Sie alle Zustände von Threads

package thread;
import static java.lang.Thread.sleep;
public class ThreadDemo18 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for(int i = 0; i < 5; i++) {
                System.out.println("线程执行中...");
                try {
                    sleep(1000);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        //线程启动之前, 状态就是 NEW
        System.out.println(t.getState());
        t.start();
        System.out.println(t.getState());
        sleep(500);
        System.out.println(t.getState());

        t.join();
        //线程运行完毕, 状态就是 TERMINATED
        System.out.println(t.getState());
    }
}

Ich denke du magst

Origin blog.csdn.net/m0_73648729/article/details/134590508
Empfohlen
Rangfolge