Es fiel mir schwer, Java-Multithreading von Grund auf neu zu erstellen. Organisieren Sie es also

Auslösen

Es war, als ich "Java 100 Knock" von Just System machte, um die Technologie zu verbessern. https://github.com/JustSystems/java-100practices/blob/master

Ich hatte ein Problem mit Multithreading und brauchte zwei Stunden, um eine Frage zu beantworten. Darüber hinaus gab es verschiedene Teile, die ich nicht gut verstanden habe, auch wenn ich sie unterschiedlich gegoogelt und gelöscht habe, sodass ich sie als Memorandum organisieren werde.

Ich habe es vor langer Zeit im Unterricht gemacht, aber ich wusste nicht einmal, wie man es benutzt, also konnte ich mich nicht daran erinnern ...

Dieser Artikel befasst sich oben mit dem Problem des 100-Klopf-Multithreading.

Beachten

Ich lerne, damit es Fehler geben kann Ich versuche nicht so viel wie möglich falsche Dinge zu schreiben, aber das ist nicht der Fall! Ich würde mich sehr freuen, wenn Sie einen Kommentar abgeben könnten.

Darüber hinaus gibt es eine Beschreibung der Prämisse, dass Sie den oben erwähnten Java 100-Schlag gesehen haben. Bitte beachten Sie.

Multithread-Grundlagen in Java

Die Erklärung war hier leicht zu verstehen. https://eng-entrance.com/java-thread

Verwenden Sie die Methode run (), um die Thread-Klasse zu erben, oder verwenden Sie in einer Klasse, die die Runnable-Schnittstelle implementiert. Die Grundform besteht darin, den Prozess, den Sie in mehreren Threads ausführen möchten, zu überschreiben und zu beschreiben. (Thread kann erstellt werden, ohne eine Klasse zu definieren ... Siehe unten)

Lassen Sie uns ein Beispiel machen. Erstellen Sie zwei Threads, die unterschiedliche Zeichenfolgen für die Standardausgabe ausgeben, und führen Sie sie parallel aus.

ThreadSample.java


/**
 *Grundfaden
 *
 */
public class ThreadSample extends Thread{
	// run()Überschreiben und schreiben Sie den Prozess darin
	@Override
	public void run() {
		for(int i = 0; i < 300; i++) {
			System.out.println("Multi-"+i);
		}
	}
}

ThreadSampleMain.java


/**
 *Grundlegende Thread-Ausführungsklasse
 *Parallel zu ThreadSample ausführen
 *
 */
public class ThreadSampleMain {

	public static void main(String[] args) {
		//Neu eine Klasse, die Thread erbt
		ThreadSample thread = new ThreadSample();
		// .start()Wenn Sie die Methode ausführen, führen Sie sie aus()Der in Werken geschriebene Prozess
		thread.start();
		for(int i=0; i < 300; i++) {
			System.out.println("Maine-"+i);
		}
	}

}

Wenn ich versuche, es zu bewegen, sieht es so aus.

(Abkürzung)
Maine-297
Multi-0 #Der Inhalt des anderen Threads ist nicht verfügbar, obwohl Main hier noch nicht fertig ist
Multi-1
Multi-2
Multi-3
Multi-4
Multi-5
Multi-6
Multi-7
Multi-8
Multi-9
Multi-10
Multi-11
Multi-12
Multi-13
(Abkürzung)

Dieser einfachste Thread kann jedoch nicht garantiert werden, wenn der ThreadSample-Thread bei jeder Ausführung ausgeführt wird. Es gibt verschiedene Mechanismen, die dies garantieren können.

Sie können Threads verwenden, ohne einen Namen zu deklarieren

Ich habe eine Instanz von ThreadSample mit dem Namensthread im obigen Code erstellt, aber Sie können sie verwenden, ohne den Namen zu deklarieren. Wenn ich Main umschreibe, sieht es so aus.

ThreadSampleMain2


public class ThreadSampleMain2 {

	public static void main(String[] args) {
		//Neu eine Klasse, die Thread erbt und so startet, wie sie ist
		new ThreadSample().start();
		for(int i=0; i < 300; i++) {
			System.out.println("Maine-"+i);
		}
	}
}

Wenn man sich das Antwortbeispiel von 100 Schlägen ansieht, gibt es viele solcher Notationen. Ich schäme mich zu sagen, dass ich nicht wusste, dass ich den Methodenausführungsprozess so schreiben kann, wie er in dem neuen Objekt ist, das ich in Java ausgeführt habe.

Sie können es verwenden, ohne es zu einer Klasse zu machen

In Java 100 gezeigte Muster klopfen auf Q041-Antwortbeispiele usw.

ThreadSampleMain3


public class ThreadSampleMain3 {

	public static void main(String[] args) {
		//Führen Sie den neuen anonymen Thread unverändert aus
		new Thread() {
			@Override
			public void run() {
				for(int i = 0; i < 300; i++) {
					System.out.println("Multi-"+i);
				}
			}
		}.start();

		for(int i=0; i < 300; i++) {
			System.out.println("Maine-"+i);
		}
	}

}

Ich habe bisher noch nicht gesehen, dass es tatsächlich in Unternehmenssystemen verwendet wird, aber es scheint, dass es auf diese Weise geschrieben werden kann.

Runnable in Thread-Instanz setzen?

Wenn Sie sich das Antwortbeispiel für Java 100 Knock ansehen, gibt es viele Muster, die start () der Instanz der Thread-Klasse zugewiesen werden, ohne die Klasse, die Runnable implementiert, direkt zu starten (). ..

Zum Beispiel unten, zitiert aus dem Antwortbeispiel von Q040

public class Answer040 implements Runnable {
    
    /**
     *040 Antwort.
     *Stapelspur von nicht erfassten Ausnahmen
     *Standardfehler mit aktueller Zeit ausgeben.
     * 
     * @Parameterargumente werden nicht verwendet.
     */
    public static void main(final String[] args) {
        Thread thread = new Thread(new Answer040());
        
        //Registrieren Sie einen Handler, der UncaughtExceptionHandler mit der Methode setUncaughtException implementiert.
        thread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        
        //Ausführung des Hauptthreads.
        thread.start();
    }
    
    /**
     *Thread ausführen.
     */
    public void run() {
        //schlafen.
        try {
             Thread.sleep(500L);
        } catch (InterruptedException e) {
             e.printStackTrace();
        }
        
        Thread subThread = new Thread(new SubThread());
        
        //Verknüpfen Sie einen Handler, der UncaughtExceptionHandler implementiert, mit einem Unterfaden.
        subThread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        
        //Subthread-Ausführung.
        subThread.start();
    }
}

Nachdem ich diese Beispielantwort gelesen hatte, hatte ich eine Frage.

――Warum überschreiben Sie run ()? Ist es nicht gut, subThread () direkt neu zu starten und zu starten ()? --Was ist der Schlaf vor run ()?

Sollten wir zum Beispiel in der Lage sein, dasselbe zu tun, indem wir es so umschreiben?

public class Answer040-2 implements Runnable {

    public static void main(final String[] args) {
        //Hinweis:SubThread ist eine Klasse, die Runnable implementiert
        SubThread sub = new SubThread();
        sb.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        sub.start();
    }

Abschließend kann ich nicht. ** (Kompilierungsfehler)

Gründe für die Registrierung einer ausführbaren Implementierungsinstanz in Thread

Hier im obigen Code

public class Answer040 implements Runnable {

    /**
     *040 Antwort.
     *Stapelspur von nicht erfassten Ausnahmen
     *Standardfehler mit aktueller Zeit ausgeben.
     * 
     * @Parameterargumente werden nicht verwendet.
     */
    public static void main(final String[] args) {
        Thread thread = new Thread(new Answer040()); //← hier
(Abkürzung)

Werfen wir einen Blick auf Javadoc, um zu sehen, wie es ist, eine Instanz, die Runnable erbt, zunächst in das Argument der Thread-Instanz einzufügen.

public Thread(Runnable target) Weisen Sie ein neues Thread-Objekt zu. Dieser Konstruktor hat den gleichen Effekt wie Thread (null, target, gname) (gname ist der neu generierte Name). Der automatisch erstellte Name hat die Form "Thread-" + n, wobei n eine Ganzzahl ist. Parameter: target - Ein Objekt, das eine Ausführungsmethode enthält, die beim Starten dieses Threads aufgerufen wird. Wenn null, führt die Ausführungsmethode dieser Klasse nichts aus.

Das war's. Wenn es einen Konstruktor gibt, der genauso funktioniert. Ich werde dort auch einen Blick darauf werfen.

public Thread(ThreadGroup group, Runnable target, String name) Weist ein neues Thread-Objekt zu, das zu der von der Gruppe referenzierten Thread-Gruppe gehört, wobei das Ziel das Ausführungsobjekt und der Name der Name ist. Wenn ein Sicherheitsmanager vorhanden ist, wird die checkAccess-Methode mit ThreadGroup als Argument aufgerufen. Wenn die checkPermission-Methode direkt oder indirekt vom Konstruktor einer Unterklasse aufgerufen wird, die die Methode getContextClassLoader oder setContextClassLoader überschreibt, wird sie außerdem mit den Berechtigungen RuntimePermission ("enableContextClassLoaderOverride") aufgerufen.

Mit anderen Worten, der Thread-Konstruktor verfügt über einen Konstruktor, der die Thread-Gruppe (ThreadGroup) und den Thread-Namen (String) weglassen kann, und der Konstruktor mit Runnable als Argument, der im vorherigen Code erwähnt wurde, weist automatisch die Thread-Gruppe und den Thread-Namen zu. ..

Was dieser Konstruktor also tut, ist

Weisen Sie ein neues Thread-Objekt zu.

Es scheint, dass. Lassen Sie uns herausfinden, was es bedeutet, ein Thread-Objekt zuzuweisen. https://www.task-notes.com/entry/20151031/1446260400

Gemäß der obigen Site wird die Thread-Klasse wie ** running run () des als Argument übergebenen Runnable-Objekts ** implementiert. Mit anderen Worten, wenn Sie Ihren eigenen Thread an das Argument Runnable übergeben, müssen Sie dies mit run () ausführen. Wir haben auch festgestellt, dass die Implementierung von Runnable besser ist als ** erweitert Thread (es sei denn, Sie überschreiben etwas anderes als run () **. Vielleicht sollten Sie auf Erweiterungen verzichten, da Sie nicht mehrmals erben können.

Aus diesen wird an dieser Stelle die Schlussfolgerung gezogen, dass ** es nicht direkt neu ist, sondern an Thread übergeben wird, da es die run () -Methode der Superklasse java.lang.Thread ** verwendet. Ich gehe davon aus, dass dies für die später beschriebene Thread-Gruppe und ExceptionHandler-Methode geeignet ist.

Warum vor dem Subthreading schlafen?

Hier im vorherigen Antwortbeispiel

(Weggelassen)
/**
     *Thread ausführen.
     */
    public void run() {
        //schlafen.
        try {
             Thread.sleep(500L); //← hier
        } catch (InterruptedException e) {
             e.printStackTrace();
        }
        
        Thread subThread = new Thread(new SubThread());
        
        //Verknüpfen Sie einen Handler, der UncaughtExceptionHandler implementiert, mit einem Unterfaden.
        subThread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        
        //Subthread-Ausführung.
        subThread.start();
    }

Dieser Prozess kam oft vor und mein Senior bei der Arbeit sagte auch: "Ich weiß nicht warum, aber wenn ich das nicht einsetze, wird es nicht funktionieren." Ich habe verschiedene Brillen ausprobiert, aber es kam nicht heraus.

Liegt es daran, dass setUncaughtExceptionHandler () möglicherweise nicht erfolgreich ist, es sei denn, Sie warten einen Moment, nachdem der direkt in die Hauptmethode geschriebene Thread gestartet wurde ()? Ich vermute. Ich werde es vorerst als Magie im Auge behalten.

Nebenbei: Über setUncaughtExceptionHandler ()

Dies ist die Methode, die im vorherigen Java 100 Knock Q040 verwendet werden muss. Wenn Sie eine Klasse erstellen, die eine Schnittstelle mit dem Namen UncaughtExceptionHandler implementiert und uncaughtException (Thread, Trowable) darin überschreibt, wird diese Methode ausgeführt, bevor ein Thread eine Ausnahme auslöst, die nicht abgefangen werden konnte. Namens. ** Nützlich zum Beispiel Protokollausgabe von Ausnahmen ** Referenz: https://javazuki.com/articles/uncaught-exception-handler-usage.html

Und dies ist eine Methode von Java.lang.Thread. ** Runnable hat es nicht, daher scheint es besser, eine Instanz der Runnable-Implementierung in Thread zu platzieren und zu verwenden **. Der Grund, warum ich die subThread-Klasse im vorherigen Kapitel nicht plötzlich neu erstellen konnte, war, dass ich versucht habe, eine Methode aufzurufen, die nicht vorhanden war, sodass ein Kompilierungsfehler aufgetreten ist.

Registrieren Sie sich bei einer Thread-Gruppe

Thread-Gruppe: Ein Mechanismus, mit dem Sie Threads gruppieren, die Anzahl der aktiven Elemente überwachen, mehrere Threads anhalten usw. können. Wenn Sie eine Thread-Gruppe verwenden, können Sie zum Zeitpunkt der Registrierung einen Namen für den Thread festlegen und diesen im Rahmen des Namens in der Thread-Gruppe verwalten, sodass er ohne Vermischung mit normalen Variablen gut zu sein scheint.

Notation, die in der Antwort auf 100 Klopffragen 35 erscheint https://github.com/JustSystems/java-100practices/blob/master/contents/035/README.md

Unten ** zitiert aus der Antwort auf Q35 **

public final class Answer035 implements Runnable {
    /*Fadengruppe A.. */
    private static ThreadGroup groupA = new ThreadGroup("GroupA");
    
    /*Fadengruppe B.. */
    private static ThreadGroup groupB = new ThreadGroup("GroupB");
    
    /**
     *Gruppe A,Führen Sie jeweils 100 Threads von B-Threads aus.
     */
    @Override public void run() {
        for (int i = 0; i < 100; i++) {
            new Thread(groupA, new ThreadRun(), "thread" + i).start();
        }
        
        for (int i = 0; i < 100; i++) {
            new Thread(groupB, new ThreadRun(), "thread" + i).start();
        }
    }
    
    /**
     *Geben Sie die Anzahl der aktiven Threads in jeder Threadgruppe aus.
     *
     * @param point Count count
     */
    public static void printActiveCount(int point) {
        System.out.println("Active Threads in Thread Group " + groupA.getName() +
            " at point(" + point + "):" + " " + groupA.activeCount());
    
        System.out.println("Active Threads in Thread Group " + groupB.getName() +
            " at point(" + point + "):" + " " + groupB.activeCount());
    }
    
    /**
     *035s Antwort.
     *Threads für jede Threadgruppe ausführen,
     *Standardausgabe der Anzahl der aktiven Threads in jedem Thread.
     *
     * @Parameterargumente werden nicht verwendet.
     */    
    public static void main(String[] args) throws InterruptedException {
        /*Ordnen Sie einen neuen Thread zu. */
        Thread thread = new Thread(new Answer035());
        
        /*Führen Sie den Thread aus. */
        thread.start();
        
        //Geben Sie die Anzahl der aktiven Threads aus.
        for (int i = 1 ;; i++) {
            printActiveCount(i);
            thread.sleep(1000L);
            
            //Verlassen Sie die Schleife, wenn der aktive Thread 0 erreicht.
            if (groupA.activeCount() == 0 && groupB.activeCount() == 0) {
                break;
            }
        }
    }
}

Der Override-Teil von run () davon. Es wird versucht, eine eigene Thread-Vererbungsklasse ThreadRun in der Thread-Gruppe zu registrieren. Bei der Verwaltung in einer Thread-Gruppe können Sie einen Thread-Klassenkonstruktor verwenden, der Thread als zweites Argument verwendet und dort ein neues erstellt, ohne die Namen mehrerer Threads nacheinander zu deklarieren.

Exklusive Verarbeitung zwischen Threads

In mehreren Threads verwende ich diesen Prozess, wenn ich in Schwierigkeiten bin, wenn ich ihn vor diesem Prozess mache. Es gibt verschiedene Methoden, und aus diesem Bereich wird es sofort kompliziert, daher werde ich nach Methoden zusammenfassen

Glossar

Gewindesicher

Dies bedeutet, dass einige Prozesse und einige Prozesse keine Probleme verursachen, selbst wenn sie parallel ausgeführt werden. Es fühlt sich an wie eine "thread-sichere Methode".

Es ist in Ordnung, wenn Sie nur Variablen ausgeben, die wie zuvor völlig unabhängig sind. Es ist jedoch threadsicher, eine Methode zu haben, die auf eine statische Variable verweist, und eine Methode, die sie neu schreibt, und vor dem Umschreiben und Auslösen einer NullPointerException darauf zu verweisen. Abwesend.

Es gibt verschiedene Verarbeitungsmethoden, um die Thread-Sicherheit zu gewährleisten. Stoppen Sie den anderen, während Sie einen ausführen. Referenzquelle: https://wa3.i-3-i.info/word12456.html

Thread-Pool

Es ist schneller, den Thread wiederzuverwenden, als einen neuen Thread zu erstellen. Daher ist es eine Methode, den erstellten Thread wiederzuverwenden. Der Thread-Pool wird von Executor (Executor Service) erstellt.

synchronisiertes Muster zu verwenden

Ein Prozess, der durch Hinzufügen von "synchronisiert" zur Methodendeklaration von Java geschrieben wurde. Methoden mit dieser Funktion werden nicht gleichzeitig ausgeführt, und die Ausführung kann durch Methoden wie notify () und wait () gesteuert werden. Ich spreche über mich selbst, aber selbst als ich ein schlampiger Student war, erinnerte ich mich nur an diese beiden Dinge, so dass ich eine bittere Erinnerung daran habe, etwas Dummes zu sagen, wie "Multithreading oder Synchronisieren".

Dies kommt sogar mit 100 Java-Schlägen heraus, aber ich blieb stecken, als ich es versuchte, also machen Sie sich eine Notiz.

Muster, die Sie nicht tun sollten

Von Java 100 klopfen Q041

Verwenden Sie wait () und notify (), um "Ganzzahlen von 1 bis 10000 hinzuzufügen und das Ergebnis in einer globalen Variablen zu speichern" und "den Wert der globalen Variablen nach Beendigung der Operation von Thread A auf Standardausgabe zu setzen". Implementieren Sie einen "Ausgabe" -Thread B und ein Programm, das die Threads A und B ungefähr zur gleichen Zeit startet.

スレッドA:Q41_ThreadA.java スレッドB:Q41_ThreadB.java 処理2つとグローバル変数のクラス:Q41_number.java 実行用:Q41.java Es wurde wie folgt implementiert.

Q41_number


public class Q41_number {

	public static long number = 0;

	public synchronized void addNumber() {
		System.out.println("add number...");
		for(long i = 1; i <= 10000; i++) {
			number += i;
		}
		System.out.println("end");
		//Benachrichtigen Sie, dass es fertig ist
		notify();
	}

	public synchronized void showNumber() {
		try {
			System.out.println("waiting...");
			wait();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(number);
	}
}

Q41_ThreadA


public class Q41_ThreadA implements Runnable{
	Q41_number q41 = new Q41_number();

	@Override
	public void run() {
		// wait()Vorher benachrichtigen()Dann wird es unendlich wiederholt, also warten Sie eine Weile
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
		q41.addNumber();
	}
}

Q41_ThreadB


public class Q41_ThreadB implements Runnable{
	Q41_number q41 = new Q41_number();

	@Override
	public void run() {
		//Bewegen Sie es etwas vor Faden A.
        try {
            Thread.sleep(500L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
		q41.showNumber();
	}
}

Q41


public class Q41 {

	public static void main(String[] args) {
		Thread threadA = new Thread(new Q41_ThreadA());
		Thread threadB = new Thread(new Q41_ThreadB());
		//Lauf
		threadA.start();
		threadB.start();
	}
}

Bei Ausführung ** Q41_number # showNumber () stoppt bei wait () und funktioniert nicht mehr **

Grund

http://www.ne.jp/asahi/hishidama/home/tech/java/thread.html Ich habe in den Kommentaren im synchronisierten Kapitel dieser Seite geschrieben.

// ↑ Auch wenn func1 () und func2 () derselben Instanz gleichzeitig von verschiedenen Threads aufgerufen werden, wird nur einer von ihnen ausgeführt. // Gleiches gilt für den Aufruf von func1 () und func1 () derselben Instanz aus verschiedenen Threads. // Wenn es sich um eine andere Instanz handelt, ist sie nicht exklusiv, da das Sperrobjekt unterschiedlich ist und gleichzeitig ausgeführt wird.

Im vorherigen Code unterscheiden sich die Q41_number-Instanzen von ThreadA und ThreadB. ** Instanzen mit synchronisierten Methoden sollten von mehreren Threads gemeinsam genutzt werden. ** ** **

Über flüchtig

Kann beim Definieren eines Feldes eingestellt werden. Damit wird das Feld während der Compileroptimierung nicht zwischengespeichert. Es ist flauschig, aber wenn ich sage: "Dieser feldspezifische Thread hätte umgeschrieben werden sollen, aber als ich ihn debuggte, wurde er nicht umgeschrieben ...", vermute ich, dass der Cache verwendet wird.

Die folgenden Websites beschreiben den Cache und die dadurch verursachten Phänomene. https://www.sejuku.net/blog/65848

Q017 verwendet dies, wenn es sich um ein Java 100-Klopfen handelt.

Definition der Ausführungsreihenfolge und Wartezeit für jeden Thread

Dies ist nützlich, wenn Sie mehrere Threads haben und diese nach Thread sortieren möchten. Verwenden Sie java.util.concurrent.CountDownLatch. Dies verwendet await (), das wartet, bis der zum Zeitpunkt der Definition festgelegte numerische Wert Null wird, und countDownLatch (), das den numerischen Wert um 1 reduziert, um das Warten zu realisieren, bis alle Threads abgeschlossen sind.

Recommended Posts

Es fiel mir schwer, Java-Multithreading von Grund auf neu zu erstellen. Organisieren Sie es also
Java hatte eine QR-Code-Erstellungsbibliothek namens QRGen, die ZXing gut umschließt, also habe ich es versucht
Es fiel mir schwer, MariaDB 10.5.5 unter CentOS 8 von Sakura VPS zu installieren
Android: Es fiel mir schwer, die HTML-Datei auf der SD-Karte anzuzeigen
Ich habe versucht, eine Java-Methode von ABCL zu verwenden
[Lösung] Ein Memo, das mir schwer fiel, weil sich das Format der Sinatra-Validierung geändert hat.
Eine Geschichte über die Schwierigkeiten beim Versuch, PHP 7.4 auf CentOS 8 von GCE zu erstellen
[Hinweis] Erstellen Sie mit Docker eine Java-Umgebung von Grund auf neu
Ich habe einen Wrapper erstellt, der KNP von Java aus aufruft
Java SE 13 (JSR388) wurde veröffentlicht, also habe ich es versucht