Qu'est-ce que Java Multithreading
Java fournit un mécanisme pour gérer plusieurs tâches simultanément (simultanément et indépendamment). Plusieurs threads coexistent dans le même processus JVM, ils partagent donc le même espace mémoire. Par rapport à plusieurs processus, la communication entre plusieurs threads est plus légère. À ma compréhension, Java Multithreading est entièrement pour améliorer l'utilisation du processeur. Les threads Java ont 4 états: nouveau, coulant, bloqué et mort. La clé est bloquante. Bloquer signifie attendre. Le fil de blocage ne participe pas à l'attribution de la tranche temporelle du répartiteur de thread, donc naturellement il n'utilisera pas le CPU. Dans un environnement multithread, ces fils non bloqués s'exécutent et utilisent pleinement le CPU.
Un résumé de 40 questions
1. À quoi sert le multi-threading?
Une question qui peut sembler absurde à beaucoup de gens: je peux simplement utiliser le multi-threading, mais à quoi sert? À mon avis, cette réponse est encore plus absurde. Le soi-disant "savoir ce qui est vrai" est "savoir ce qui est vrai", "savoir ce qui est vrai", "pourquoi utiliser" est "savoir ce qui est vrai". Ce n'est qu'en atteignant le niveau de "savoir ce qui est vrai" peut être considéré comme étant en mesure d'appliquer un point de connaissance librement. Ok, laissez-moi vous dire ce que je pense de ce problème:
(1) Offrez le jeu complet aux avantages du CPU multi-core
Avec l'avancement de l'industrie, les ordinateurs portables d'aujourd'hui, les ordinateurs de bureau et même les serveurs d'applications commerciaux sont tous doubles, et 4 cœurs, 8 cœurs ou même 16 cœurs ne sont pas rares. S'il s'agit d'un programme unique, 50% est gaspillé sur le CPU double cœur et 75% est gaspillé sur le CPU à 4 cœurs. Le soi-disant "multi-threading" sur un processeur monocore est un faux multi-threading. Le processeur ne traitera qu'un morceau de logique en même temps, mais les threads changent relativement rapidement, ce qui ressemble à plusieurs threads s'exécutent "en même temps". Multithreading sur des processeurs multi-core est le véritable multithreading. Il peut permettre à votre logique multi-segments de fonctionner en même temps et multi-threading. Il peut vraiment donner un jeu complet aux avantages des processeurs multicœurs pour atteindre l'objectif de profiter pleinement du processeur.
(2) Empêcher le blocage
Du point de vue de l'efficacité du fonctionnement du programme, un processeur à noyau unique ne donnera pas seulement un jeu complet aux avantages du multi-lancement, mais réduira plutôt l'efficacité globale du programme, car l'exécution du multi-threading sur un processeur à noyau unique entraînera la commutation de contexte de thread, ce qui réduira l'efficacité globale du programme. Cependant, nous devons toujours appliquer le multithreading aux processeurs monocœurs pour empêcher le blocage. Imaginez simplement, si un CPU à noyau unique utilise un seul thread, tant que le thread est bloqué, par exemple, en lisant à distance une certaines données, le pair n'est pas revenu pendant longtemps et n'a pas défini un délai, puis votre programme entier cessera de fonctionner avant le retour des données. Le multi-threading peut empêcher ce problème. Plusieurs fils fonctionnent en même temps. Même si le code d'un thread est empêché de lire les données, cela n'affectera pas l'exécution d'autres tâches.
(3) facile à modéliser
C'est un autre avantage qui n'est pas si évident. Supposons qu'il y ait une grande tâche, une programmation unique à thread, alors vous devez considérer beaucoup, et il est plus difficile de construire l'ensemble du modèle de programme. Cependant, si vous cassez cette grande tâche A en plusieurs petites tâches, la tâche B, la tâche C et la tâche D, créez un modèle de programme séparément et exécutez ces tâches séparément via plusieurs threads, ce sera beaucoup plus simple.
2. Comment créer des threads
Un problème plus courant est généralement deux:
(1) Hériter de la classe de threads
(2) implémenter l'interface runnable
Quant à laquelle est le meilleur, il va sans dire que ce dernier est certainement meilleur, car la façon d'implémenter les interfaces est plus flexible que la méthode de la classe d'héritage, et peut également réduire le couplage entre les programmes. La programmation axée sur l'interface est également le cœur des six principaux principes des modèles de conception.
3. La différence entre la méthode start () et la méthode Run ()
Ce n'est que lorsque la méthode start () sera appelée les caractéristiques du multi-threading afficher, et le code de la méthode run () de différents threads sera exécuté alternativement. Si vous appelez simplement la méthode run (), le code sera exécuté de manière synchrone. Vous devez attendre que le code de la méthode RUN () d'un thread soit exécuté avant qu'un autre thread puisse exécuter le code dans sa méthode run ().
4. La différence entre l'interface coulable et l'interface appelée
Il y a un peu de question profonde, et cela montre également l'étendue des connaissances apprises par un programmeur Java.
La valeur de retour de la méthode run () dans l'interface runnable est void, et ce qu'il fait est juste d'exécuter le code dans la méthode run (); La méthode Call () dans l'interface appelée a une valeur de retour, qui est un générique, et peut être utilisée pour obtenir les résultats de l'exécution asynchrone en conjonction avec Future et FutureTask.
Il s'agit en fait d'une fonctionnalité très utile, car une raison majeure pour laquelle le multithreading est plus difficile et plus complexe que le threading unique est que le multithreading est plein d'inconnues. Un certain fil a-t-il été exécuté? Depuis combien de temps un fil a-t-il été exécuté? Les données que nous attendons sont-elles lorsqu'un thread est exécuté? Il ne savait pas que tout ce que nous pouvons faire est d'attendre l'exécution de cette tâche multithread. Calable + Future / FutureTask peut obtenir les résultats de l'exécution multithread et peut annuler la tâche du thread lorsque le temps d'attente est trop long et que les données requises ne sont pas obtenues. C'est vraiment utile.
5. La différence entre Cyclicbarrier et CountdownLatch
Les deux classes qui semblent un peu similaires peuvent être utilisées pour indiquer que le code s'exécute à un certain point sous java.util.concurrent. La différence entre les deux est:
(1) Après qu'un fil de bar Cyclique fonctionne à un certain point, le fil cesse qui s'arrête. Jusqu'à ce que tous les threads atteignent ce point, tous les threads ne seront plus en cours d'exécution; Le compte à rebours ne l'est pas. Une fois qu'un thread fonctionne à un certain point, il donne juste une certaine valeur -1 et le fil continue de s'exécuter.
(2) Cyclicbarrier ne peut évoquer qu'une seule tâche, le compte à rebours peut évoquer plusieurs tâches
(3) Cyclicbarrier peut être réutilisé, le compte à rebours ne peut pas être réutilisé et la valeur de comptage n'est pas réutilisée, et le compte à rebours ne sera plus utilisé
6. Le rôle des mots clés volatils
Un problème très important est que chaque programmeur Java qui apprend et applique le multi-threading doit le maîtriser. La condition préalable à la compréhension du rôle du mot-clé volatil est de comprendre le modèle de mémoire Java. Je ne parlerai pas du modèle de mémoire Java ici. Vous pouvez vous référer au point 31. Il existe deux fonctions principales du mot clé volatil:
(1) Le multithreading tourne principalement autour des deux caractéristiques de la visibilité et de l'atomicité. Les variables modifiées par le mot clé volatil garantissent leur visibilité entre plusieurs threads, c'est-à-dire que chaque fois que la variable volatile est lue, ce doit être les dernières données.
(2) L'exécution du code sous-jacente n'est pas aussi simple que la langue de haut niveau que nous voyons - les programmes Java. Son exécution est Java Code -> bytecode -> Exécuter le code C / C ++ correspondant en fonction du code bytecode -> C / C ++ est compilé dans le langage d'assemblage -> en interagissant avec le circuit matériel. En réalité, afin d'obtenir de meilleures performances, le JVM peut réorganiser les instructions, et certains problèmes inattendus peuvent survenir sous un multi-thread. L'utilisation du volatile interdit la réorganisation sémantique, ce qui réduit bien sûr l'efficacité de l'exécution du code dans une certaine mesure
D'un point de vue pratique, un rôle important du volatile est de se combiner avec les CAS pour assurer l'atomicité. Pour plus de détails, vous pouvez vous référer aux classes sous le package java.util.concurrent.atomic, comme AtomicInteger.
7. Qu'est-ce que la sécurité des fils
Une autre question théorique, il y a beaucoup de réponses. Je voudrais en donner un que je pense personnellement être la meilleure explication: si votre code peut toujours obtenir le même résultat lorsqu'il est exécuté sous le multithreading et le seul lancement, alors votre code est en file d'attente.
Il y a quelque chose qui mérite d'être mentionné sur ce problème, c'est-à-dire qu'il y a plusieurs niveaux de sécurité des fils:
(1) immuable
Comme String, Integer, Long, etc., ce sont tous des types finaux. Aucun thread ne peut modifier leurs valeurs. Si vous voulez les changer, vous n'en crérez pas un nouveau. Par conséquent, ces objets immuables peuvent être utilisés directement dans un environnement multithread sans aucun moyen de synchronisation.
(2) Sécurité des fils absolus
Quel que soit l'environnement d'exécution, l'appelant n'a pas besoin de mesures de synchronisation supplémentaires. Pour ce faire, vous devez généralement payer beaucoup de frais supplémentaires. En Java, ce ne sont en fait pas des classes de filetage. Cependant, il existe également des classes qui sont absolument thread-safe, telles que CopyOnwriteArrayList et CopyOnwriteArraySet
(3) Sécurité des filetages relatifs
La sécurité relative des fils est ce que nous appelons habituellement la sécurité des filetages. Par exemple, les méthodes vectorielles, add et supprimées sont des opérations atomiques et ne seront pas interrompues, mais elle est limitée à cela. Si un thread traversant un certain vecteur, un thread est ajouté en même temps, une conception concurrentModification Exception se produira dans 99% des cas, ce qui est le mécanisme fasciné.
(4) Les fils ne sont pas sûrs
Il n'y a rien à dire à ce sujet. ArrayList, LinkedList, Hashmap, etc. sont tous des classes non thread.
8. Comment obtenir un fichier de vide de thread dans Java
Pour des problèmes tels que Dead Loop, Impatelock, Blocking, lent Page Opening, etc., Frapper Thread Dump est le meilleur moyen de résoudre le problème. Le ce que l'on appelle le vide est la pile de threads. Il y a deux étapes pour obtenir la pile de threads:
(1) Obtenez le PID du thread, vous pouvez utiliser la commande JPS et vous pouvez également utiliser ps -ef | Grep Java dans l'environnement Linux
(2) Imprimez la pile de threads, vous pouvez utiliser la commande jstack pid, et vous pouvez également utiliser Kill -3 PID dans un environnement Linux
De plus, la classe de threads fournit une méthode getStackTrace () qui peut également être utilisée pour obtenir la pile de threads. Il s'agit d'une méthode d'instance, donc cette méthode est liée à une instance de thread spécifique. Chaque fois que vous obtenez la pile en cours d'exécution sur un fil spécifique.
9. Que se passe-t-il si un fil a une exception d'exécution?
Si cette exception n'est pas capturée, le fil cessera de s'exécuter. Un autre point important est: si ce thread contient un moniteur d'un certain objet, le moniteur d'objet sera publié immédiatement
10. Comment partager les données entre deux threads
Partagez simplement des objets entre les threads, puis évoquez et attendez via attendre / notifier / notifier, attendre / signal / signalall. Par exemple, le blocage de la file d'attente est conçu pour partager des données entre les threads.
11. Quelle est la différence entre la méthode du sommeil et la méthode d'attente
Cette question est fréquemment posée, la méthode de sommeil et la méthode d'attente peuvent être utilisées pour abandonner le CPU pendant une certaine période de temps. La différence est que si le thread conserve le moniteur d'un objet, la méthode de sommeil n'abandonnera pas le moniteur de cet objet et la méthode d'attente abandonnera le moniteur de cet objet.
12. Quel est le rôle du modèle de consommation producteur?
Cette question est théorique, mais importante:
(1) Améliorer l'efficacité opérationnelle de l'ensemble du système en équilibrant la capacité de production de la capacité de consommation des producteurs et des consommateurs, ce qui est le rôle le plus important du modèle de consommateur des producteurs.
(2) Découplage, c'est la fonction accompagnée du modèle producteur et consommateur. Le découplage signifie qu'il y a moins de liens entre les producteurs et les consommateurs. Plus les connexions sont moins, plus elles peuvent se développer par elles-mêmes sans recevoir des contraintes mutuelles.
13. Quelle est l'utilisation du filetage
En termes simples, Threadlocal est une pratique d'échanger de l'espace pour le temps. Chaque thread maintient un threadlocal.ThreadLocalmap implémenté par la méthode d'adresse ouverte pour isoler les données et ne pas partager les données, donc il n'y aura naturellement pas de problèmes de sécurité des threads.
14. Pourquoi les méthodes attend () et notify () / notifyall () sont appelées dans le bloc de synchronisation
Ceci est forcé par JDK. La méthode attend () et notify () / notifyall () doivent d'abord obtenir le verrou de l'objet avant d'appeler
15. Quelle est la différence entre la méthode Wait () et Notify () / notifyall () lors de l'abandon du moniteur d'objet
La différence entre la méthode wait () et la méthode notify () / notifyall () lors de l'abandon du moniteur d'objet est que la méthode attend () libère immédiatement le moniteur d'objet, tandis que la méthode notify () / notifyall () attendra l'exécution du code restant du thread avant d'abandonner le moniteur d'objet.
16. Pourquoi utiliser la piscine de threads
Évitez la création et la destruction fréquentes des fils pour obtenir la réutilisation des objets de thread. De plus, l'utilisation de pools de threads peut également contrôler le nombre de simultanés de manière flexible selon le projet.
17. Comment détecter si un thread détient un moniteur d'objet
J'ai également vu une question d'interview multithread sur Internet pour savoir qu'il existe un moyen de déterminer si un thread détient un moniteur d'objet: la classe de thread fournit une méthode HoldSlock (objet obj), qui renvoie vrai si et seulement si le moniteur de l'objet obj est détenu par un fil. Notez qu'il s'agit d'une méthode statique, ce qui signifie que "un certain thread" fait référence au thread actuel.
18. La différence entre synchronisé et reentrantlock
Synchronisé est le même mot-clé que si, sinon, pour et bien que, et ReentrantLoc est une classe, qui est la différence essentielle entre les deux. Étant donné que Reentrantlock est une classe, il offre des caractéristiques de plus en plus flexibles que synchronisées, qui peuvent être héritées, ont des méthodes et ont diverses variables de classe. L'évolutivité du reentrantlock que synchronisé se reflète en plusieurs points:
(1) Reentrantlock peut définir le temps d'attente pour acquérir la serrure, évitant ainsi l'impasse
(2) Reentrantlock peut obtenir diverses informations de verrouillage
(3) Reentrantlock peut implémenter de manière flexible
De plus, les mécanismes de verrouillage des deux sont en fait différents. Le reentrantlock sous-jacent appelle la méthode du parc de dangereuse à verrouiller, et l'opération synchronisée devrait être le mot de marque dans l'en-tête de l'objet, je ne peux pas être sûr de cela.
19. Quelle est la concurrence de la currenthashmap
La concurrence de concurrenthashmap est la taille du segment, qui est de 16 par défaut, ce qui signifie qu'au plus 16 threads peuvent fonctionner en même temps. Il s'agit également du plus grand avantage de la Currententhashmap sur le hashtable. En tout cas, le hashtable peut-il avoir deux threads en même temps obtenir les données dans le hashtable?
20. Qu'est-ce que ReadWritelock
Tout d'abord, indiquons clairement que ce n'est pas que le reentrantlock n'est pas bon, c'est juste que Reentrantlock est limité à certains moments. Si Reentrantlock est utilisé, il peut être prévenant des incohérences de données causées par le thread A Data A et Thread B Data de lecture. Cependant, de cette manière, si les données de lecture du thread C et le thread D sont également des données de lecture, les données de lecture ne modifieront pas les données. Il n'est pas nécessaire de le verrouiller, mais il le verrouille toujours, ce qui réduit les performances du programme.
Pour cette raison, le verrouillage de lecture Readwritelock est né. ReadWriteLock est une interface de verrouillage en lecture-écriture. ReentrAnTreadWriteLock est une implémentation concrète de l'interface ReadWriteLock, qui réalise la séparation de la lecture et de l'écriture. Les verrous de lecture sont partagés et les verrous en écriture sont exclusifs. Lire et lire et lire ne s'exclueront pas mutuellement. LIRE ET ÉCRIRE, ÉCRIRE, ÉCRIRE ET LIRE, ÉCRIRE ET ÉCRIRE s'effondrera mutuellement, améliorant les performances de la lecture et de l'écriture.
21. Qu'est-ce que Futuretask
Ceci est en fait mentionné plus tôt. FutureTask représente une tâche de fonctionnement asynchrone. Une classe d'implémentation spécifique de Calable peut être transmise dans FutureTask, qui peut attendre le résultat de cette opération asynchrone à obtenir, déterminer s'il a été achevé et annuler la tâche. Bien sûr, comme FutureTask est également une classe d'implémentation de l'interface Runnable, FutureTask peut également être placé dans le pool de threads.
22. Comment trouver quel thread utilise le CPU le plus long de l'environnement Linux
C'est un problème plus pratique, et je pense que ce problème est assez significatif. Vous pouvez faire ceci:
(1) Obtenez le PID du projet, JPS ou PS -EF | grep java, qui a été mentionné précédemment
(2) Top -H -P Pid, l'ordre ne peut pas être modifié
Cela imprimera le pourcentage de temps de CPU que le projet actuel prend pour chaque fil. Notez que celui ici est LWP, qui est le numéro de thread du thread natif du système d'exploitation. My Notebook Mountain ne déploie pas de projets Java dans l'environnement Linux, il n'y a donc aucun moyen de prendre des captures d'écran et des démonstrations. Si l'entreprise déploie un projet à l'aide d'un environnement Linux, vous pouvez l'essayer.
L'utilisation de "Top -H -P PID" + "JPS PID" peut facilement trouver une pile de threads qui occupe un CPU élevé, positionnant ainsi la raison de l'occupation du CPU élevé, ce qui est généralement dû aux opérations de code inappropriées qui conduisent à une boucle morte.
Enfin, permettez-moi de mentionner que le LWP joué avec "TOP -H -P PID" est décimal, et le numéro de fil local joué avec "JPS PID" est hexadécimal. Après la conversion, vous pouvez localiser la pile de threads actuelle qui occupe un CPU élevé.
23. Programmation Java Écrivez un programme qui provoquera une impasse
J'ai vu cette question pour la première fois et j'ai pensé que c'était une très bonne question. Beaucoup de gens savent à quoi ressemble la blocage: le fil A et le fil B attendent que les serrures les uns des autres provoquent une boucle morte infinie pour continuer le programme. Bien sûr, cela ne se limite qu'à cela. Si vous demandez comment rédiger un programme de blocage, vous ne le saurez pas. Pour le dire franchement, vous ne comprenez pas ce qu'est une impasse. Si vous comprenez une théorie, vous aurez terminé. Vous pouvez essentiellement ne pas voir le problème de la blocage dans la pratique.
Pour vraiment comprendre ce qu'est une impasse, cette question n'est pas difficile, il y a quelques étapes:
(1) Deux threads maintiennent deux objets d'objet: Lock1 et Lock2 respectivement. Ces deux serrures servent de verrous pour les blocs de code synchrones;
(2) Dans la méthode RUN () du thread 1, le bloc de code de synchronisation obtient d'abord le verrouillage de l'objet de Lock1, Thread.Sleep (xxx), le temps ne prend pas trop, 50 millisecondes sont presque les mêmes, puis obtient le verrouillage de l'objet de Lock2. Cela est principalement fait pour empêcher le thread 1 d'obtenir en continu les verrous d'objets de deux objets: Lock1 et Lock2.
(3) L'exécution du thread 2) (Dans la méthode, le bloc de code de synchronisation obtient d'abord le verrouillage de l'objet2, puis acquiert le verrouillage de l'objet1. Bien sûr, le verrouillage de l'objet1 est déjà maintenu par le serrure du thread 1, et le thread 2 doit attendre le thread 1 pour libérer le verrouillage de l'objet1.
De cette façon, après le thread 1 "Sleeps" et le thread 2 a acquis l'objet Lock2. Le thread 1 essaie d'acquérir le verrouillage de l'objet2 pour le moment et est bloqué. À l'heure actuelle, une impasse est formée. Je n'écrirai plus le code, il occupe beaucoup d'espace. Java Multithreading 7: Iadlock Cet article contient l'implémentation du code des étapes ci-dessus.
24. Comment réveiller un fil de blocage
Si le thread bloque en raison de l'appel Wait (), Sleep () ou Join (), il peut interrompre le thread et le réveiller en lançant une interruption d'Exception; Si le thread rencontre un blocage IO, il est impuissant car IO est implémenté par le système d'exploitation et le code Java ne peut pas contacter directement le système d'exploitation.
25. Quelle aide les objets immuables aident-ils à multithreading
Un problème mentionné ci-dessus est que les objets immuables garantissent la visibilité de la mémoire des objets, et il n'y a pas besoin de synchronisation supplémentaire pour lire des objets immuables, ce qui améliore l'efficacité de l'exécution du code.
26. Qu'est-ce que le changement de contexte multithread
La commutation de contexte multithread fait référence au processus de commutation de contrôle du processeur d'un thread en cours d'exécution vers un autre thread prêt et en attendant que les droits d'exécution du CPU soient obtenus.
27. Si la file d'attente de pool de fil est pleine lorsque vous soumettez une tâche, ce qui se passera pour le moment
Si vous utilisez LinkedBlockingQueue, c'est-à-dire des files d'attente illimitées, cela n'a pas d'importance. Continuez à ajouter des tâches à la file d'attente de blocage et attendez l'exécution, car LinkedBlockingQueue peut être presque considérée comme une file d'attente infinie et peut stocker les tâches infiniment; Si vous utilisez une file d'attente limitée, par exemple, ArrayBlockingQueue, la tâche sera d'abord ajoutée à l'arrayblockingQueue. Si le ArrayBlockingQueue est plein, le RejectEdExecutionHandler utilisera la politique de rejet pour gérer les tâches complètes, et la valeur par défaut est abortpolicy.
28. Quel est l'algorithme de planification de threads utilisé dans Java?
Style préemptif. Une fois qu'un thread a utilisé le processeur, le système d'exploitation calculera une priorité totale en fonction des données telles que la priorité du thread, la faim de thread, etc. et alloue la prochaine fois, tranche à un thread pour l'exécution.
29. Quelle est la fonction de Thread.Sleep (0)
Cette question est liée à la question ci-dessus, et je suis tous ensemble. Étant donné que Java utilise un algorithme de planification de threads préventif, il peut se produire qu'un fil obtient souvent un contrôle du processeur. Afin d'autoriser certains threads avec une priorité relativement faible pour obtenir le contrôle du processeur, Thread.Sleep (0) peut être utilisé pour déclencher manuellement les tranches de temps d'allocation du système d'exploitation, qui est également une opération pour équilibrer le contrôle du processeur.
30. Qu'est-ce que Spin
De nombreux codes synchronisés ne sont qu'un code très simple, et le temps d'exécution est très rapide. Le verrouillage des threads en attente à ce moment peut être une opération non valable, car le blocage des threads implique la commutation de l'état d'utilisateur et du noyau. Étant donné que le code en synchronisé s'exécute très rapidement, vous pourriez aussi bien laisser le thread attendre que le verrou ne soit pas bloqué, mais faites plutôt des boucles occupées à la limite de synchronisée. C'est un spin. Si vous avez fait plusieurs boucles occupées et constatez que le verrou n'a pas été obtenu, puis le bloquez, cela peut être une meilleure stratégie.
31. Quel est le modèle de mémoire Java
Le modèle de mémoire Java définit une spécification pour l'accès multi-threading à la mémoire Java. Le modèle de mémoire Java doit être pleinement expliqué, mais je ne peux pas l'expliquer clairement dans quelques phrases ici. Permettez-moi de le résumer brièvement.
Plusieurs parties du modèle de mémoire Java:
(1) Le modèle de mémoire Java divise la mémoire en mémoire principale et mémoire de travail. L'état de la classe, c'est-à-dire les variables partagées entre les classes, est stockée dans la mémoire principale. Chaque fois qu'un thread Java utilise ces variables dans la mémoire principale, il lira les variables dans la mémoire principale et les permettra d'exister dans sa propre mémoire de travail. Lors de l'exécution de son propre code de thread, il utilise ces variables et exploite celui de sa propre mémoire de travail. Une fois le code de thread exécuté, la dernière valeur sera mise à jour vers la mémoire principale.
(2) Plusieurs opérations atomiques sont définies pour faire fonctionner des variables dans la mémoire principale et la mémoire de travail
(3) définir les règles d'utilisation des variables volatiles
(4) Avant, c'est-à-dire le principe de première occurrence, définit certaines règles dans lesquelles le fonctionnement a doit se produire d'abord en fonctionnement B. Par exemple, le code devant le flux de contrôle dans le même thread doit se produire en premier dans le code derrière le flux de contrôle, l'action de libération du déverrouillage de verrouillage ne doit pas se produire en premier dans l'action de l'association du même verrouillage, etc.. Si un certain morceau de code ne se conforme pas à toutes les règles en cours, alors ce morceau de code doit être en sécurité.
32. Qu'est-ce que CAS
CAS, nom complet Compare and Set, est compare-ensemble. Supposons qu'il existe trois opérandes: valeur de mémoire V, l'ancienne valeur attendue A, la valeur B à modifier. Si et seulement si la valeur A attendue A et la valeur de mémoire V sont les mêmes, la valeur de mémoire sera modifiée en B et renvoyée vraie, sinon rien ne sera fait et FALSE sera renvoyé. Bien sûr, CAS doit coopérer avec la variable volatile, afin de s'assurer que la variable obtenue à chaque fois est la dernière valeur de la mémoire principale. Sinon, l'ancienne valeur attendue A sera toujours une valeur A qui ne changera pas pour un fil. Tant qu'une certaine opération CAS échoue, elle ne réussira jamais.
33. Qu'est-ce que le verrouillage optimiste et le verrou pessimiste
(1) Lock optimiste: Tout comme son nom, il est optimiste quant aux problèmes de sécurité des fils causés par des opérations simultanées. Optimiste Lock estime que la concurrence ne se produit pas toujours, il n'a donc pas besoin de maintenir le verrou et comparera - définir ces deux actions en tant qu'opération atomique pour essayer de modifier les variables en mémoire. S'il échoue, cela signifie qu'un conflit se produit, et il devrait y avoir une logique de réessayer correspondante.
(2) Lock pessimiste: Tout comme son nom, il est pessimiste quant aux problèmes de sécurité des fils causés par des opérations simultanées. Les écluses pessimistes croient que la concurrence se produira toujours. Par conséquent, chaque fois qu'une ressource est utilisée, elle tiendra un verrou exclusif, tout comme synchronisé, qu'il soit verrouillé directement et que la ressource sera utilisée.
34. Qu'est-ce que AQS
Parlons brièvement des AQ. Le nom complet d'AQS est AbstractqueUeEdsychronizer. Il doit s'agir d'un synchroniseur de file d'attente abstrait lorsqu'il est traduit.
Si la base de Java.util.concurrent est CAS, AQS est le cœur de l'ensemble du package de concurrence Java et ReentrantLoc, CountdownLatch, Semaphore, etc. tous l'utilisent. AQS relie en fait toute l'entrée sous la forme d'une file d'attente bidirectionnelle. Par exemple, reentrantlock. Tous les fils d'attente sont placés dans une entrée et connectés dans une file d'attente bidirectionnelle. Si le thread précédent utilise ReentrantLock, la première entrée de la file d'attente bidirectionnelle commence réellement en cours d'exécution.
AQS définit toutes les opérations sur les files d'attente bidirectionnelles, mais n'ouvre que les méthodes Trylock et TryRelease à utiliser. Les développeurs peuvent réécrire les méthodes Trylock et TryRelease en fonction de leur propre implémentation pour implémenter leurs propres fonctions de concurrence.
35. Sécurité en filetage du mode singleton
Un problème de cliché, la première chose à dire est que la sécurité du fil du singleton signifie que: les instances d'une certaine classe ne seront créées qu'une seule fois dans un environnement multithread. Il existe de nombreuses façons d'écrire le modèle Singleton, permettez-moi de résumer:
(1) Écriture du modèle singleton de l'homme affamé: sécurité du fil
(2) Rédaction du singleton paresseux: non-thread-safe
(3) Écrivez le mode singleton de verrouillage à double vérification: sécurité du fil
36. Quelle est la fonction du sémaphore
Le sémaphore est un sémaphore, et sa fonction est de limiter le nombre de concurrence dans un certain bloc de code. Semaphore a un constructeur qui peut passer Int Integer n, indiquant que seuls N threads peuvent accéder à un certain morceau de code. Si N est dépassé, veuillez attendre qu'un thread termine le bloc de code et entrez le thread suivant. À partir de cela, nous pouvons voir que si l'intégralité int n = 1 s'est écoulée dans le constructeur de sémaphore équivaut à devenir synchronisé.
37. Il n'y a qu'une seule instruction "Return Count" dans la méthode SIZE () de Hashtable, alors pourquoi avez-vous toujours besoin de synchroniser?
C'est une confusion que j'avais auparavant, et je me demande si vous avez pensé à cette question. S'il y a plusieurs instructions dans une méthode et qu'elles fonctionnent toutes la même variable de classe, alors si vous n'ajoutez pas de verrous dans un environnement multithread, cela provoquera inévitablement des problèmes de sécurité du thread. Ceci est facile à comprendre, mais la méthode Taille () n'a clairement qu'une seule instruction, alors pourquoi avez-vous toujours besoin d'ajouter des verrous?
En ce qui concerne ce problème, je l'ai compris en travaillant et en étudiant lentement, et il y a deux raisons principales:
(1) Un seul thread peut exécuter la méthode de synchronisation de la classe fixe en même temps, mais pour la méthode asynchrone de la classe, plusieurs threads peuvent y accéder en même temps. Il y a donc un problème. Peut-être que le thread A ajoute des données lors de l'exécution de la méthode de put de hashtable, et le thread B peut appeler la méthode Taille () normalement pour lire le nombre d'éléments actuels dans le hashtable. La valeur lue n'est peut-être pas la dernière. Peut-être que Thread A a ajouté les données, mais sans taille ++, le thread B a déjà lu la taille. Ainsi, pour le fil B, la taille de lecture doit être inexacte. Après avoir ajouté une synchronisation à la méthode Taille (), cela signifie que le thread B appelle la méthode Taille () qu'après que le thread A a appelé la méthode de put, ce qui assure la sécurité du thread
(2) Le CPU exécute du code, mais ce n'est pas du code Java. C'est très important et vous devez vous en souvenir. Le code Java est finalement traduit en code d'assemblage pour l'exécution, et le code d'assemblage est le code qui peut vraiment interagir avec les circuits matériels. Même si vous voyez qu'il n'y a qu'une seule ligne de code Java, et même si vous voyez que le code Java est compilé, il n'y a qu'une seule ligne de bytecode générée, cela ne signifie pas que pour la couche sous-jacente, il n'y a qu'une seule opération pour cette instruction. La phrase "Return Count" suppose qu'elle est traduite en trois instructions d'assemblage à exécuter, et il est tout à fait possible que le thread change après l'exécution de la première phrase.
38. Quel thread est le constructeur de la classe de thread et du bloc statique appelé par quel thread
C'est une question très délicate et rusée. N'oubliez pas: le constructeur et le bloc statique de la classe de thread sont appelés par le thread où se trouve la nouvelle classe de thread, et le code de la méthode d'exécution est appelé par le thread lui-même.
Si l'instruction ci-dessus vous confond, alors permettez-moi de vous donner un exemple, supposons que le nouveau thread1 est dans Thread2 et que le nouveau thread2 est en fonction principale, alors:
(1) Le constructeur et le bloc statique de Thread2 sont appelés par le thread principal, et la méthode Run () de Thread2 est appelée par thread2 lui-même
(2) Le constructeur et le bloc statique de Thread1 sont appelés par Thread2, et la méthode Run () de Thread1 est appelée par Thread1 lui-même
39. Quel est le meilleur choix entre la méthode de synchronisation et le bloc de synchronisation?
Synchroniser les blocs, ce qui signifie que le code à l'extérieur du bloc de synchronisation est exécuté de manière asynchrone, ce qui améliore l'efficacité du code plus efficace que la synchronisation de l'ensemble de la méthode. Veuillez connaître un principe: la gamme moins de synchronisation
Le mieux.
Avec cet article, je voudrais mentionner que bien que plus la plage de synchronisation soit petite, mieux c'est, il existe toujours une méthode d'optimisation appelée grossièreté de verrouillage dans les machines virtuelles Java, qui est d'augmenter la plage de synchronisation. C'est utile. Par exemple, StringBuffer est une classe de filetage. Naturellement, la méthode APPEND () la plus couramment utilisée est une méthode de synchronisation. Lorsque nous écrivons du code, nous ajouterons à plusieurs reprises la chaîne, ce qui signifie le verrouillage à plusieurs reprises -> déverrouillage, ce qui n'est pas bon pour les performances, car cela signifie que la machine virtuelle Java doit basculer à plusieurs reprises entre l'état du noyau et l'état utilisateur sur ce thread. Par conséquent, la machine virtuelle Java effectue une opération de verrouillage sur le code appelé par la méthode de plusieurs annexes, étendant plusieurs opérations d'ajout à la tête et à la queue de la méthode d'ajout et en la transformant en un grand bloc de synchronisation. Cela réduit le nombre de temps de déverrouillage de verrouillage, améliorant efficacement l'efficacité de l'exécution du code.
40. Comment utiliser des pools de threads pour les entreprises avec une concurrence élevée et un temps d'exécution de tâche courte? Comment utiliser des pools de threads pour les entreprises avec une faible concurrence et un temps d'exécution de tâches longs? Comment utiliser des pools de threads pour les entreprises avec une concurrence élevée et un temps d'exécution de service long?
Ceci est une question que j'ai vue sur le site Web de programmation simultanée. Je pose cette question en dernier et j'espère que tout le monde pourra voir et y penser, car cette question est très bonne, très pratique et très professionnelle. En ce qui concerne cette question, mon opinion personnelle est:
(1) Temps d'exécution de la concurrence et de la tâche courte élevée, le nombre de threads de pool de threads peut être défini sur CPU Core Number +1 pour réduire la commutation du contexte du thread
(2)并发不高、任务执行时间长的业务要区分开看:
a)假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以加大线程池中的线程数目,让CPU处理更多的业务
b)假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(1)一样吧,线程池中的线程数设置得少一些,减少线程上下文的切换
(3)并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考(2)。最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件对任务进行拆分和解耦。
Java线程阻塞(Blocked)的类型:
调用sleep函数进入睡眠状态,Thread.sleep(1000)或者TimeUnit.SECONDS.sleep(1),sleep不会释放锁。
等待(wait)某个事件,分为两种,(wait,notify,notifyAll),(await, signal,signalAll) ,后面会详细介绍。wait和await会释放锁,且必须在获取到锁的环境才能调用。
等待锁,synchronized和lock环境中,锁已经被别的线程拿走,等待获取锁。
IO阻塞(Blocked),比如网络等待,文件打开,控制台读取。System.in.read()。