J'ai eu du mal à faire du multithreading Java à partir de zéro, alors organisez-le

Déclencheur

C'était quand j'ai fait "Java 100 Knock" fait par Just System pour améliorer la technologie. https://github.com/JustSystems/java-100practices/blob/master

J'étais coincé avec un problème lié au multi-threading, et il m'a fallu deux heures pour répondre à une question. De plus, même si je googlé diversement et que je l'ai effacé, il y avait différentes parties que je ne comprenais pas bien, donc je vais l'organiser sous forme de mémorandum.

Je l'ai fait en classe il y a longtemps, mais je ne savais même pas comment m'en servir, donc je ne m'en souvenais pas ...

Cet article aborde le problème du multithreading à 100 coups ci-dessus.

Remarquer

J'étudie donc il peut y avoir des erreurs J'essaie de ne pas écrire autant que possible de mauvaises choses, mais ce n'est pas le cas! Je serais très heureux si vous pouviez commenter.

En outre, il y a une description de la prémisse que vous avez vu le coup Java 100 mentionné ci-dessus. Notez s'il vous plaît.

Principes de base du multithread en Java

L'explication était facile à comprendre ici. https://eng-entrance.com/java-thread

Héritez de la classe Thread, ou dans une classe qui implémente l'interface Runnable, utilisez la méthode run (). Le formulaire de base consiste à remplacer et à décrire le processus que vous souhaitez exécuter dans plusieurs threads. (Le fil peut être créé sans définir de classe ... Voir ci-dessous)

Faisons un échantillon. Créez deux threads qui génèrent des chaînes de caractères différentes vers la sortie standard et exécutez-les en parallèle.

ThreadSample.java


/**
 *Fil de base
 *
 */
public class ThreadSample extends Thread{
	// run()Remplacer et écrire le processus dedans
	@Override
	public void run() {
		for(int i = 0; i < 300; i++) {
			System.out.println("Multi-"+i);
		}
	}
}

ThreadSampleMain.java


/**
 *Classe d'exécution de thread de base
 *Exécuter en parallèle avec ThreadSample
 *
 */
public class ThreadSampleMain {

	public static void main(String[] args) {
		//Nouvelle classe qui hérite de Thread
		ThreadSample thread = new ThreadSample();
		// .start()Lorsque vous exécutez la méthode, exécutez()Le processus écrit en œuvres
		thread.start();
		for(int i=0; i < 300; i++) {
			System.out.println("Principale-"+i);
		}
	}

}

Quand j'essaye de le déplacer, ça ressemble à ça.

(Abréviation)
Principale-297
Multi-0 #Le contenu de l'autre thread est sorti même si Main n'est pas terminé ici
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
(Abréviation)

Cependant, ce thread le plus simple n'est pas garanti où le thread ThreadSample sera exécuté chaque fois qu'il est exécuté. Il existe différents mécanismes qui peuvent garantir cela.

Vous pouvez utiliser des threads sans déclarer de nom

J'ai créé une instance de ThreadSample avec le nom thread dans le code ci-dessus, mais vous pouvez l'utiliser sans déclarer le nom. Quand je réécris Main, ça ressemble à ça.

ThreadSampleMain2


public class ThreadSampleMain2 {

	public static void main(String[] args) {
		//Nouvelle classe qui hérite de Thread et la démarre telle quelle
		new ThreadSample().start();
		for(int i=0; i < 300; i++) {
			System.out.println("Principale-"+i);
		}
	}
}

En regardant l'exemple de réponse de 100 coups, il existe de nombreuses notations de ce type. J'ai honte de dire que je ne savais pas que je pourrais écrire le processus d'exécution de la méthode tel qu'il est dans le nouvel objet que je faisais en Java.

Vous pouvez l'utiliser sans en faire une classe en premier lieu

Modèles vus dans les exemples de réponse Java 100 knock Q041, etc.

ThreadSampleMain3


public class ThreadSampleMain3 {

	public static void main(String[] args) {
		//Exécutez le nouveau fil anonyme tel quel
		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("Principale-"+i);
		}
	}

}

Je ne l'ai pas vu jusqu'à présent utilisé dans les systèmes d'entreprise, mais il semble qu'il puisse être écrit de cette manière.

Mettre Runnable dans l'instance Thread?

En regardant l'exemple de réponse de Java 100 knock, de nombreux modèles start () sont affectés à l'instance de la classe Thread sans démarrer directement () la classe qui implémente Runnable. ..

Par exemple, ci-dessous, cité à partir de l'exemple de réponse de Q040

public class Answer040 implements Runnable {
    
    /**
     *Réponse de 040.
     *Trace de pile des exceptions non interceptées
     *Erreur standard de sortie avec l'heure actuelle.
     * 
     * @paramètres param non utilisés.
     */
    public static void main(final String[] args) {
        Thread thread = new Thread(new Answer040());
        
        //Inscrire un gestionnaire qui implémente UncaughtExceptionHandler avec la méthode setUncaughtException.
        thread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        
        //Exécution du thread principal.
        thread.start();
    }
    
    /**
     *Exécuter le fil.
     */
    public void run() {
        //dormir.
        try {
             Thread.sleep(500L);
        } catch (InterruptedException e) {
             e.printStackTrace();
        }
        
        Thread subThread = new Thread(new SubThread());
        
        //Associer un gestionnaire qui implémente UncaughtExceptionHandler avec un sous-thread.
        subThread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        
        //Exécution de sous-thread.
        subThread.start();
    }
}

Après avoir lu cet exemple de réponse, j'avais une question.

――Pourquoi écrasez-vous run ()? N'est-il pas bon de créer directement un nouveau subThread () et de le démarrer ()?

Par exemple, devrions-nous être capables de faire de même en le réécrivant comme ça?

public class Answer040-2 implements Runnable {

    public static void main(final String[] args) {
        //Remarque:SubThread est une classe qui implémente Runnable
        SubThread sub = new SubThread();
        sb.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        sub.start();
    }

En conclusion, ** je ne peux pas. ** (Erreur de compilation)

Raisons d'enregistrer une instance d'implémentation exécutable dans Thread

Ici dans le code ci-dessus

public class Answer040 implements Runnable {

    /**
     *Réponse de 040.
     *Trace de pile des exceptions non interceptées
     *Erreur standard de sortie avec l'heure actuelle.
     * 
     * @paramètres param non utilisés.
     */
    public static void main(final String[] args) {
        Thread thread = new Thread(new Answer040()); //← ici
(Abréviation)

En premier lieu, jetons un coup d'œil à Javadoc pour voir à quoi cela ressemble de placer une instance qui hérite de Runnable dans l'argument de l'instance Thread.

public Thread(Runnable target) Attribuez un nouvel objet Thread. Ce constructeur a le même effet que Thread (null, target, gname) (gname est le nom nouvellement généré). Le nom créé automatiquement prend la forme "Thread-" + n, où n est un entier. Paramètres: target: objet contenant une méthode d'exécution appelée au démarrage de ce thread. Si null, la méthode run de cette classe ne fait rien.

Je vois. S'il y a un constructeur qui fonctionne de la même manière. Je vais y jeter un œil également.

public Thread(ThreadGroup group, Runnable target, String name) Attribue un nouvel objet Thread qui appartient au groupe de threads référencé par le groupe, avec target comme objet d'exécution et le nom spécifié comme nom. Si un gestionnaire de sécurité existe, la méthode checkAccess est appelée avec ThreadGroup comme argument. En outre, si elle est appelée directement ou indirectement par le constructeur d'une sous-classe qui remplace la méthode getContextClassLoader ou setContextClassLoader, la méthode checkPermission sera appelée avec les autorisations RuntimePermission ("enableContextClassLoaderOverride").

En d'autres termes, le constructeur Thread a un constructeur qui peut omettre le groupe de threads (ThreadGroup) et le nom du thread (String), et le constructeur qui a Runnable comme argument, qui a été mentionné dans le code précédent, affecte automatiquement le groupe de threads et le nom du thread. ..

Donc, ce que fait ce constructeur est

Attribuez un nouvel objet Thread.

Il paraît que. Découvrons ce que signifie attribuer un objet Thread. https://www.task-notes.com/entry/20151031/1446260400

Selon le site ci-dessus, la classe Thread est implémentée comme ** exécutant run () de l'objet Runnable passé en argument **. En d'autres termes, passer votre propre thread à l'argument Runnable signifie l'exécuter avec run (). Nous avons également constaté que implémenter Runnable est meilleur que ** étend Thread (sauf si vous remplacez autre chose que run () **. Vous devriez peut-être vous abstenir d'étendre car vous ne pouvez pas hériter plusieurs fois.

De ceux-ci, la conclusion obtenue à ce stade est que ** ce n'est pas directement nouveau mais passé à Thread car il utilise la méthode run () de la superclasse java.lang.Thread **. Je présume que cela convient au groupe de threads et à la méthode ExceptionHandler décrits plus tard.

Pourquoi dormir avant de sous-threader

Ici dans l'exemple de réponse précédent

(Omis)
/**
     *Exécuter le fil.
     */
    public void run() {
        //dormir.
        try {
             Thread.sleep(500L); //← ici
        } catch (InterruptedException e) {
             e.printStackTrace();
        }
        
        Thread subThread = new Thread(new SubThread());
        
        //Associer un gestionnaire qui implémente UncaughtExceptionHandler avec un sous-thread.
        subThread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        
        //Exécution de sous-thread.
        subThread.start();
    }

Ce processus revenait souvent, et mon aîné au travail disait: «Je ne sais pas pourquoi, mais ça ne marche que si je l'ai mis en place. J'ai essayé différents googles, mais cela n'est pas sorti.

Est-ce parce que setUncaughtExceptionHandler () peut ne pas réussir à moins que vous n'attendiez un moment après que Thread écrit directement dans la méthode main ait démarré ()? Je devine. Pour le moment, je vais garder cela à l'esprit comme une magie pour le moment.

En dehors: À propos de setUncaughtExceptionHandler ()

C'est la méthode qui doit être utilisée dans le précédent Java 100 knock Q040. Si vous créez une classe qui implémente une interface appelée UncaughtExceptionHandler et remplacez uncaughtException (Thread, Trowable) dans celle-ci, lorsqu'un thread lève une exception qui n'a pas pu être interceptée, cette méthode sera exécutée juste avant que le thread se termine anormalement. Appelé. ** Utile par exemple pour la sortie du journal des exceptions ** Référence: https://javazuki.com/articles/uncaught-exception-handler-usage.html

Et c'est une méthode de Java.lang.Thread. ** Il n'existe pas dans Runnable, il semble donc préférable de mettre une instance d'implémentation Runnable dans Thread et de l'utiliser **. La raison pour laquelle je n'ai pas pu soudainement changer la classe subThread dans le chapitre précédent était que j'essayais d'appeler une méthode qui n'était pas là, donc j'ai eu une erreur de compilation.

S'inscrire à un groupe de discussion

Groupe de threads: mécanisme qui vous permet de regrouper des threads, de surveiller le nombre d'actifs, de suspendre plusieurs threads, etc. Si vous utilisez un groupe de threads, vous pouvez définir un nom pour le thread au moment de l'enregistrement et le gérer dans la portée du nom dans le groupe de threads, donc cela semble être bon sans mélanger avec des variables ordinaires.

Notation qui apparaît dans la réponse à 100 questions frappantes 35 https://github.com/JustSystems/java-100practices/blob/master/contents/035/README.md

Ci-dessous, ** extrait de la réponse à la Q35 **

public final class Answer035 implements Runnable {
    /*Groupe de fils A. */
    private static ThreadGroup groupA = new ThreadGroup("GroupA");
    
    /*Groupe de fils B. */
    private static ThreadGroup groupB = new ThreadGroup("GroupB");
    
    /**
     *groupe A,Exécuter 100 threads chacun des threads B.
     */
    @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();
        }
    }
    
    /**
     *Sortie du nombre de threads actifs dans chaque groupe de threads.
     *
     * @nombre de points de paramètre
     */
    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());
    }
    
    /**
     *Réponse de 035.
     *Exécuter des threads pour chaque groupe de threads,
     *Sortie standard du nombre de threads actifs dans chaque thread.
     *
     * @paramètres param non utilisés.
     */    
    public static void main(String[] args) throws InterruptedException {
        /*Allouer un nouveau fil. */
        Thread thread = new Thread(new Answer035());
        
        /*Exécutez le fil. */
        thread.start();
        
        //Sortie du nombre de threads actifs.
        for (int i = 1 ;; i++) {
            printActiveCount(i);
            thread.sleep(1000L);
            
            //Quitter la boucle lorsque le thread actif atteint 0.
            if (groupA.activeCount() == 0 && groupB.activeCount() == 0) {
                break;
            }
        }
    }
}

La partie de remplacement de run () de this. Il essaie d'enregistrer sa propre classe d'héritage Thread ThreadRun dans un groupe de threads. Lors de la gestion dans un groupe Thread, vous pouvez utiliser un constructeur de classe Thread qui peut prendre Thread comme deuxième argument et en créer un nouveau sans déclarer les noms de plusieurs threads un par un.

Traitement exclusif entre les threads

Dans plusieurs threads, j'utilise ce processus lorsque j'ai des problèmes si je le fais avant ce processus. Il existe différentes méthodes, et cela devient tout de suite compliqué à partir de ce domaine, je vais donc résumer par méthode

Glossaire

Fil sûr

Cela signifie que certains processus et certains processus ne posent aucun problème même s'ils sont exécutés en parallèle. Cela ressemble à une "méthode thread-safe".

C'est bien si vous sortez simplement des variables qui sont complètement indépendantes comme avant, mais il est thread-safe d'avoir une méthode qui fait référence à une variable statique et une méthode qui la réécrit, et d'y faire référence avant de réécrire et de lancer une NullPointerException. Absent.

Il existe différentes méthodes de traitement pour le rendre thread-safe. Lors de l'exécution de l'un, arrêtez l'autre. Source de référence: https://wa3.i-3-i.info/word12456.html

Pool de threads

Il est plus rapide de réutiliser le thread que de créer un nouveau thread, c'est donc une méthode pour réutiliser le thread créé. Le pool de threads est créé par Executor (Executor Service).

Pattern synchronisé à utiliser

Un processus écrit en ajoutant "synchronized" à la déclaration de méthode java. Les méthodes avec ceci ne seront pas exécutées en même temps, et l'exécution peut être contrôlée par des méthodes telles que notify () et wait (). Je parle de moi, mais même lorsque j'étais un élève bâclé, je ne me souvenais que de ces deux choses, donc j'ai un souvenir amer d'avoir dit quelque chose de stupide, comme "multi-threading ou synchronisation".

Cela sort même avec 100 coups Java, mais je suis resté coincé quand je l'ai essayé, alors prenez note.

Modèles que vous ne devriez pas faire

De Java 100 frapper Q041

Utilisez wait () et notify () pour "ajouter des entiers de 1 à 10000 et stocker le résultat dans une variable globale" et "définir la valeur de la variable globale sur la sortie standard après la fin de l'opération du thread A" Implémentez un thread "de sortie" B et un programme qui démarre les threads A et B à peu près en même temps.

スレッドA:Q41_ThreadA.java スレッドB:Q41_ThreadB.java 処理2つとグローバル変数のクラス:Q41_number.java 実行用:Q41.java Il a été mis en œuvre comme suit.

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");
		//Notifier que c'est fini
		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()Notifier avant()Puis il boucle à l'infini, alors attendez un moment
        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() {
		//Faites-le avancer légèrement devant le fil 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());
		//Exécuter
		threadA.start();
		threadB.start();
	}
}

Lorsqu'il est exécuté ** Q41_number # showNumber () s'arrête à wait () et cesse de fonctionner **

Raison

http://www.ne.jp/asahi/hishidama/home/tech/java/thread.html J'ai écrit dans les commentaires dans le chapitre synchronisé de ce site.

// ↑ Même si func1 () et func2 () de la même instance sont appelés à partir de différents threads en même temps, un seul d'entre eux sera exécuté. // La même chose s'applique à l'appel de func1 () et func1 () de la même instance à partir de différents threads. // S'il s'agit d'une instance différente, elle ne sera pas exclusive car l'objet de verrouillage est différent, et il sera exécuté en même temps.

Dans le code précédent, les instances Q41_number de ThreadA et ThreadB sont différentes. ** Les instances avec des méthodes synchronisées doivent être partagées par plusieurs threads. ** **

À propos de volatile

Peut être défini lors de la définition d'un champ. Avec cela, le champ ne sera pas mis en cache pendant l'optimisation du compilateur. C'est duveteux, mais quand je dis: "Ce thread spécifique à un champ aurait dû être réécrit, mais quand je le débogue, il n'a pas été réécrit ...", je soupçonne que le cache est utilisé.

Les sites suivants sont détaillés sur le cache et les phénomènes causés par celui-ci. https://www.sejuku.net/blog/65848

Q017 l'utilise lorsqu'il s'agit d'un coup Java 100.

Définition de l'ordre d'exécution et attente de fin pour chaque thread

Ceci est utile lorsque vous avez plusieurs threads et que vous souhaitez les classer par thread. Utilisez java.util.concurrent.CountDownLatch. Cela utilise wait (), qui attend que la valeur numérique définie au moment de la définition devienne zéro, et countDownLatch (), qui réduit la valeur numérique de 1, pour réaliser l'attente jusqu'à ce que tous les threads soient terminés.

Recommended Posts

J'ai eu du mal à faire du multithreading Java à partir de zéro, alors organisez-le
Java avait une bibliothèque de création de code QR appelée QRGen qui enveloppe bien ZXing, alors je l'ai essayé
J'ai eu du mal à installer MariaDB 10.5.5 sur CentOS 8 de Sakura VPS
Android: j'ai eu du mal à afficher le fichier HTML sur la carte SD
J'ai essayé de frapper une méthode Java d'ABCL
[Solution] Un mémo que j'ai eu du mal car le format de sinatra-validation a changé.
Une histoire sur le fait d'avoir du mal à construire PHP 7.4 sur CentOS 8 de GCE
[Note] Créez un environnement Java à partir de zéro avec docker
J'ai créé un Wrapper qui appelle KNP depuis Java
Java SE 13 (JSR388) est sorti alors je l'ai essayé