Cette série est basée sur le cours du raffinage des nombres en or, et afin de mieux apprendre, une série de disques a été réalisée. Cet article introduit principalement 1. Qu'est-ce qu'un thread 2.
1. Qu'est-ce que le fil
Les threads sont des unités d'exécution dans les processus
Il y a plusieurs threads dans un processus.
Un thread est une unité d'exécution dans un processus.
La raison de l'utilisation de threads est que la commutation de processus est une opération très lourde et consomme des ressources. Si vous utilisez plusieurs processus, le nombre de concurrence ne sera pas très élevé. Les threads sont des unités de planification plus petites et sont plus légères, donc les fils sont plus largement utilisés dans la conception simultanée.
En Java, le concept de threads est similaire au concept de threads au niveau du système d'exploitation. En fait, JVM mappera les threads en Java dans la zone de thread du système d'exploitation.
2. Opérations de base des threads
2.1 Diagramme d'état du thread
L'image ci-dessus montre le fonctionnement de base des threads en Java.
Lorsqu'un fil est nouveau, le fil ne fonctionne pas en fait. Il génère simplement une entité et le fil est en fait démarré lorsque vous appelez la méthode de démarrage de cette instance. Après le démarrage, il atteint l'état de course. Runnable signifie que les ressources du fil et ainsi de suite ont été préparées et peuvent être exécutées, mais cela ne signifie pas qu'il se trouve dans l'état d'exécution. En raison de la rotation des tranches de temps, le fil peut ne pas s'exécuter pour le moment. Pour nous, le fil peut être considéré comme exécuté, mais la question de savoir si elle est exécutée dépend en fait de la planification du CPU physique. Lorsque la tâche de thread est exécutée, le thread atteint l'état terminé.
Parfois, lors de l'exécution d'un fil, il est inévitable que certains verrous ou moniteurs d'objets soient appliqués. Lorsqu'il ne peut pas être récupéré, le fil sera bloqué, suspendu et atteindra l'état bloqué. Si ce thread appelle la méthode d'attente, elle est dans un état d'attente. Le thread entrant dans l'état d'attente attendra que d'autres threads l'informent. Une fois la notification faite, l'état d'attente passera de l'état d'attente à l'état coulable pour continuer l'exécution. Bien sûr, il existe deux types d'états d'attente, l'un est d'attendre indéfiniment jusqu'à ce qu'il soit informé. Il attend une période limitée. Par exemple, si vous attendez 10 secondes ou si vous n'avez pas été informé, il passera automatiquement à l'état coulable.
2.2 Créer un nouveau fil
Thread thread = new Thread ();
thread.start ();
Cela ouvre un fil.
Une chose à noter est
Thread thread = new Thread ();
thread.run ();
Vous ne pouvez pas ouvrir un nouveau fil en appelant directement la méthode d'exécution.
La méthode de démarrage appelle en fait la méthode d'exécution sur un nouveau thread de système d'exploitation. En d'autres termes, si vous appelez la méthode d'exécution directement au lieu de la méthode de démarrage, elle ne démarrera pas un nouveau thread, mais effectuera vos opérations dans l'exécution actuelle de l'appel de thread.
Thread thread = new Thread ("t1") {@Override public void run () {// TODO Méthode générée automatique Stub System.out.println (thread.currentThread (). GetName ()); }}; thread.start (); Si le démarrage est appelé, la sortie est t1thread thread = new Thread ("t1") {@Override public void run () {// TODO Méthode générée automatique Stub System.out.println (thread.currentThread (). GetName ()); }}; thread.run (); S'il est exécuté, le principal est la sortie. (Le tournage directement n'est en fait qu'un appel de fonction ordinaire, et il n'a pas atteint le rôle de multi-threading)
Il existe deux façons d'implémenter la méthode d'exécution
La première méthode consiste à remplacer directement la méthode d'exécution. Comme indiqué dans le code tout à l'heure, le moyen le plus pratique peut être réalisé en utilisant une classe anonyme.
Thread thread = new Thread ("t1") {@Override public void run () {// TODO Méthode générée automatique Stub System.out.println (thread.currentThread (). GetName ()); }}; La deuxième façon
Thread t1 = nouveau thread (new CreateThread3 ());
CreateThread3 () implémente l'interface runnable.
Dans la vidéo de Zhang Xiaoxiang, la deuxième méthode est recommandée, disant qu'elle est plus orientée objet.
2.3 terminer le fil
Thread.stop () n'est pas recommandé. Il libère tous les moniteurs
Il a été clairement indiqué dans le code source que la méthode d'arrêt est obsolète, et la raison est également expliquée dans Javadoc.
La raison en est que la méthode d'arrêt est trop "violente". Peu importe où le thread s'exécute, il cessera immédiatement de supprimer le thread.
Lorsque le thread d'écriture obtient le verrou, commence à écrire des données. Après avoir écrit ID = 1, il est arrêté et le verrou est libéré lors de la préparation à définir le nom = 1. Le fil de lecture obtient le verrouillage pour le fonctionnement de lecture. La lecture d'ID est 1 et le nom est toujours 0, ce qui provoque une incohérence des données.
La chose la plus importante est que ce type d'erreur ne lancera pas d'exception et sera difficile à détecter.
2.4 Interruption du thread
Il y a 3 façons d'interrompre le fil
Thread void public.interrupt () // Interrupteur de thread
public boolean thread.isinterrupted () // déterminer s'il est interrompu
public static booléen thread.interrupted () // déterminer s'il est interrompu et effacer l'état d'interruption actuel
Qu'est-ce que l'interruption du fil?
Si vous ne comprenez pas le mécanisme d'interruption de Java, une telle explication est très susceptible de provoquer des malentendus, et vous pensez que l'appel de la méthode d'interruption du fil interrompra certainement le fil.
En fait, l'interruption de Java est un mécanisme de collaboration. En d'autres termes, appeler la méthode d'interruption d'un objet de thread n'interrompt pas nécessairement le thread en cours d'exécution, il nécessite simplement que le thread s'interrompt au bon moment. Chaque fil a un état d'interruption booléen (pas nécessairement la propriété de l'objet, en fait, cet état n'est en effet pas un champ de thread), et la méthode d'interruption définit simplement l'état sur true. Pour les threads non bloquants, l'état d'interruption est modifié, c'est-à-dire que thread.isterrupted () reviendra vrai et n'arrêtera pas le programme;
public void run () {// thread t1 while (true) {thread.yield (); }} t1.interrupt (); Cela n'aura aucun effet pour interrompre le thread T1, mais le bit d'état d'interruption est modifié.
Si vous souhaitez terminer ce fil très gracieusement, vous devriez le faire
public void run () {while (true) {if (thread.currentThread (). isInterrupted ()) {System.out.println ("Interruted!"); casser; } Thread.yield (); }}L'utilisation d'interruptions offre certaines garanties pour la cohérence des données.
Pour les threads dans des états de blocage annuables, tels que les threads en attente de ces fonctions, Thread.Sleep (), Object.Wait () et Thread.Join (), ce thread lancera une conception interrompue après avoir reçu le signal d'interruption et remettra l'état d'interruption sur False.
Pour les threads dans le déblocage des états, vous pouvez écrire du code comme ceci:
public void run () {while (true) {if (thread.currentThread (). isInterrupted ()) {System.out.println ("Interruted!"); casser; } essayez {Thread.Sleep (2000); } catch (InterruptedException e) {System.out.println ("Interruté lorsque le sommeil"); // Définissez l'état d'interruption. Le bit du drapeau d'interruption sera effacé après avoir lancé une exception thread.currentThread (). Interrupt (); } Thread.yield (); }}2.5 Le fil est suspendu
Suspendre et reprendre les fils
suspendre () ne libérera pas le verrou
Si le verrouillage se produit avant le CV (), les deux méthodes de blocage qui se produisent sont des méthodes obsolètes et ne sont pas recommandées.
La raison en est que Suspende ne libère pas le verrou, donc aucun thread ne peut accéder à la ressource de zone critique qui est verrouillée par elle jusqu'à ce qu'elle soit reproduite par d'autres threads. Étant donné que la séquence des threads en cours d'exécution ne peut pas être contrôlée, si la méthode de curriculum vitae des autres threads est en premier, le suspendu plus tard occupera toujours le verrou, provoquant une impasse.
Utilisez le code suivant pour simuler ce scénario
Test de package; test de classe publique {objet statique u = nouveau objet (); TestsUsPendThread statique T1 = nouveau TestSUsPendThread ("T1"); TestsUsPendThread statique T2 = nouveau TestSUsPendThread ("T2"); classe statique publique TestSUsPendThread étend Thread {public TestSUsPendThread (String Name) {setName (name); } @Override public void run () {synchronisé (u) {System.out.println ("dans" + getName ()); Thread.currentThread (). Suspendre (); }} public static void main (String [] args) lève InterruptedException {t1.start (); Thread.Sleep (100); t2.start (); T1.Resume (); T2.Resume (); t1.join (); t2.join (); }} Laissez T1 et T2 rivaliser pour un verrou en même temps, le fil qui est en compétition est suspendu, puis reprend. Logiquement parlant, un fil doit être libéré par curriculum vitae après avoir concouru pour cela, puis un autre fil est en concurrence pour le verrou et les curriculum vitae.
La sortie du résultat est:
en T1
en T2
Cela signifie que les deux threads rivalisent pour le verrou, mais la lumière rouge sur la console est toujours allumée, ce qui signifie qu'il doit y avoir des threads qui n'ont pas été exécutés dans T1 et T2. Jetons-le et jetons un coup d'œil
Il a été constaté que T2 avait été suspendu. Cela crée une impasse.
2.6 Join et Yeild
Yeild est une méthode statique native. Cette méthode consiste à libérer le temps de CPU qu'il possède, puis à rivaliser avec d'autres threads (notez que les threads de Yeild peuvent toujours rivaliser pour CPU, faire attention à la différence du sommeil). Il est également expliqué dans Javadoc que Yeild est une méthode qui n'est essentiellement pas utilisée et est généralement utilisée dans le débogage et le test.
La méthode de jointure signifie attendre que d'autres threads se terminent, tout comme le code dans la section Suspende, vous voulez que le fil principal attende que T1 et T2 se terminent avant la fin. Si cela ne se termine pas, le fil principal y sera bloqué.
test de package; test de classe publique {public volatile statique int i = 0; La classe statique publique addThread étend Thread {@Override public void run () {for (i = 0; i <10000000; i ++); }} public static void main (String [] args) lève InterruptedException {addThread at = new addThread (); at.start (); at.join (); System.out.println (i); }} Si la jointure du code ci-dessus est supprimée, le thread principal s'exécutera directement et la valeur de I sera très petite. S'il y a une jointure, la valeur de l'imprimé je dois être 10000000.
Alors, comment la jointure est-elle mise en œuvre?
La nature de JOIN
tandis que (isalive ())
{
attendre (0);
}
La méthode join () peut également passer un temps, ce qui signifie attendre une période limitée, et se réveiller automatiquement après cette période.
Il y a une question comme ceci: qui informera le fil? Il n'y a pas d'endroit où appeler NOTIFY dans la classe de threads?
À Javadoc, une explication pertinente a été trouvée. Lorsqu'un thread est terminé et terminé, la méthode Notifyall sera appelée pour réveiller tous les threads en attente de l'instance de thread actuelle. Cette opération est effectuée par JVM lui-même.
Ainsi, Javadoc nous a également donné une suggestion de ne pas utiliser d'attente et de notification / notifier sur les instances de fil. Parce que JVM s'appellera, il peut être différent du résultat attendu de votre appel.
3. Fil de démon
La réalisation silencieuse de certains services systématiques en arrière-plan, telles que les fils de collecte des ordures et les fils JIT, peut être compris comme des fils de démon.
Lorsque tous les processus non-Daemon se terminent dans une application Java, la machine virtuelle Java sortira naturellement.
J'ai écrit un article sur la façon de l'implémenter dans Python, le vérifier ici.
Il est relativement simple de devenir un démon à Java.
Thread t = new DaEmont ();
t.setDaemon (true);
t.start ();
Cela ouvre un fil de démon.
Test de package; Test de classe publique {classe publique statique DaemonThread étend Thread {@Override public void run () {for (int i = 0; i <10000000; i ++) {System.out.println ("hi"); }}} public static void main (string [] args) lève InterruptedException {daemonthread dt = new DaemThread (); dt.start (); }} Lorsque le thread DT n'est pas un thread de démon, après l'exécution, nous pouvons voir la sortie de la console HI
Lorsque vous rejoignez avant de commencer
dt.setDaemon (true);
La console sort directement et n'a pas de sortie.
4. Priorité du fil
Il y a 3 variables dans la classe de threads qui définissent la priorité du thread.
public final static int min_priority = 1; public final static int norm_priority = 5; public final static int max_priority = 10; package test; public class test {public static class high étend thread {static int count = 0; @Override public void run () {while (true) {synchronisé (test.class) {count ++; if (count> 10000000) {System.out.println ("high"); casser; }}}}} Classe statique publique Low étend Thread {static int count = 0; @Override public void run () {while (true) {synchronisé (test.class) {count ++; if (count> 10000000) {System.out.println ("Low"); casser; }}}}} public static void main (String [] args) lève InterruptedException {high high = new High (); Bas bas = nouveau bas (); high.setPriority (thread.max_priority); Low.SetPriority (thread.min_priority); Low.start (); high.start (); }} Laissez un thread haute priorité et un thread à faible priorité rivaliser pour un verrou en même temps et voir lequel termine en premier.
Bien sûr, il ne doit pas nécessairement être achevé en premier avec une grande priorité. Après l'avoir exécuté plusieurs fois, j'ai constaté que la probabilité d'achèvement de grande priorité est relativement élevée, mais il est toujours possible de le terminer d'abord avec une faible priorité.
5. Opération de synchronisation de filetage de base
synchronisé et object.wait () obejct.notify ()
Pour plus de détails sur cette section, veuillez consulter un blog que j'ai écrit auparavant.
La principale chose à noter est
Il existe trois façons de verrouiller la synchronisation:
Spécifiez l'objet verrouillé: verrouillez l'objet donné et obtenez le verrouillage de l'objet donné avant d'entrer le code de synchronisation.
Agissant directement sur la méthode d'instance: il équivaut à verrouiller l'instance actuelle. Avant d'entrer dans le code de synchronisation, vous devez obtenir le verrou de l'instance actuelle.
Agissant directement sur des méthodes statiques: il équivaut à verrouiller la classe actuelle. Avant d'entrer dans le code synchrone, vous devez obtenir le verrou de la classe actuelle.
Si cela fonctionne sur la méthode d'instance, ne nouvelles deux instances différentes
Il fonctionne sur des méthodes statiques, tant que la classe est la même, car le verrouillage ajouté est une classe.classe, et deux instances différentes peuvent être nouvelles.
Comment utiliser l'attente et le notif:
Utilisez n'importe quel verrouillage, appelez l'attente et notifiez
Je n'entrerai pas dans les détails de cet article.