Maison >Java >javaDidacticiel >Résumé de 40 problèmes de multithreading Java

Résumé de 40 problèmes de multithreading Java

巴扎黑
巴扎黑original
2017-04-30 10:10:021204parcourir

Avant-propos

Il y a 21 articles multi-threads écrits dans la catégorie multi-threading Java. Les 21 articles contiennent beaucoup de contenu. Personnellement, je pense que plus vous apprenez de contenu et de connaissances complexes, plus vous devez faire un résumé approfondi. afin que vous puissiez vous en souvenir profondément et transformer ces connaissances en quelque chose qui vous appartient. Cet article résume principalement les problèmes multi-threading, donc 40 problèmes multi-thread sont répertoriés.

Certains de ces problèmes multithread proviennent de sites Web majeurs, et d'autres viennent de ma propre réflexion. Il peut y avoir des questions sur Internet, il peut y avoir des réponses à certaines questions, et il peut y en avoir certaines que tous les internautes ont lues, mais l'objectif de la rédaction de cet article est de répondre à toutes les questions selon votre propre compréhension, et vous le ferez. Je ne regarde pas les réponses en ligne, donc peut-être que certaines questions sont fausses, et j'espère que vous pourrez me corriger.

Résumé de 40 questions

1. A quoi sert le multi-threading ?

Une question qui peut paraître absurde à beaucoup de gens : tant que je peux utiliser le multi-threading, à quoi ça sert ? À mon avis, cette réponse est encore plus absurde. Ce qu'on appelle « savoir comment c'est, savoir pourquoi », « savoir comment l'utiliser » signifie simplement « savoir comment c'est », « pourquoi vous l'utilisez » signifie « savoir comment c'est seulement lorsque vous l'atteignez ». le niveau de « savoir comment c'est, savoir pourquoi il en est ainsi » peut-on dire qu'il s'agit de « savoir comment c'est ». Un point de connaissance peut être utilisé librement. OK, parlons de mon point de vue sur cette question :

(1) Profitez du processeur multicœur

Avec les progrès de l'industrie, les ordinateurs portables, les ordinateurs de bureau et même les serveurs d'applications commerciaux d'aujourd'hui sont au moins double cœur, 8 cœurs ou même 16 cœurs. S'il s'agit d'un programme monothread, alors sur un. Un processeur double cœur est gaspillé à 50 % et un processeur à 4 cœurs gaspille 75 %. Le soi-disant « multi-threading » sur un processeur monocœur est un faux multi-threading. Le processeur ne traitera qu'un élément de logique en même temps, mais la commutation entre les threads est plus rapide et il semble que plusieurs threads le soient. fonctionnant "simultanément". Le multithreading sur un processeur multicœur est le véritable multithreading. Il permet à vos multiples éléments logiques de fonctionner en même temps. Le multithreading peut vraiment tirer parti du processeur multicœur et atteindre l'objectif de rendre le fonctionnement complet. utilisation du processeur.

(2) Empêcher le blocage

Du point de vue de l'efficacité d'exécution du programme, un processeur monocœur non seulement ne profitera pas du multi-threading, mais réduira également l'efficacité globale du programme, car l'exécution de plusieurs threads sur un processeur monocœur entraînera un contexte de thread. commutation. Mais pour les processeurs monocœur, nous devons toujours utiliser le multithreading pour éviter le blocage. Imaginez simplement, si un processeur monocœur utilise un seul thread, alors tant que ce thread est bloqué, par exemple lors de la lecture de certaines données à distance, et que le homologue n'est pas encore revenu et n'a pas défini de délai d'attente, alors l'ensemble de votre programme le fera. être bloqué avant que les données ne soient renvoyées. Le multithreading peut éviter ce problème. Plusieurs threads s'exécutent en même temps, même si l'exécution du code d'un thread est bloquée lors de la lecture des données, cela n'affectera pas l'exécution des autres tâches.

(3) Facile à modéliser

C'est un autre avantage moins évident. Supposons qu'il y ait une grande tâche A, une programmation monothread, alors il y a beaucoup de choses à considérer et il est difficile de construire l'ensemble du modèle de programme. Mais si vous divisez cette grande tâche A en plusieurs petites tâches, la tâche B, la tâche C et la tâche D, établissez respectivement des modèles de programme et exécutez ces tâches séparément via plusieurs threads, ce sera beaucoup plus simple.

2. Comment créer un fil de discussion

Question relativement courante, il en existe généralement deux types :

(1) Hériter de la classe Thread

(2) Implémenter l'interface Runnable

Quant à savoir lequel est le meilleur, il va sans dire que ce dernier est meilleur, car la manière d'implémenter les interfaces est plus flexible que la manière d'hériter des classes, et peut également réduire le couplage entre les programmes. La programmation orientée interface est également au cœur. des six 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() est appelée que la fonctionnalité multithread sera affichée et que le code des méthodes run() des différents threads sera exécuté alternativement. Si vous appelez simplement la méthode run(), le code est toujours exécuté de manière synchrone. Vous devez attendre que tout 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 Runnable et l'interface Callable

C'est une question un peu profonde, et cela montre également l'étendue des connaissances qu'un programmeur Java peut acquérir.

La valeur de retour de la méthode run() dans l'interface Runnable est nulle, et elle ne sert qu'à exécuter le code dans la méthode run() ; la méthode call() dans l'interface Callable a une valeur de retour et est générique ; , peut être utilisé conjointement avec Future et FutureTask pour obtenir les résultats d'une exécution asynchrone.

Il s'agit en fait d'une fonctionnalité très utile, car une raison importante pour laquelle le multithreading est plus difficile et complexe que le monothreading est que le multithreading est plein d'inconnues. Un certain thread a-t-il été exécuté ? Depuis combien de temps un thread est-il exécuté ? Lorsqu'un thread est exécuté, les données attendues ont-elles été attribuées ? Il n'y a aucun moyen de le savoir, tout ce que nous pouvons faire est d'attendre que cette tâche multithread soit terminée. Callable+Future/FutureTask peut obtenir les résultats d'opérations multi-thread, et peut annuler la tâche du thread si le temps d'attente est trop long et que les données requises ne sont pas obtenues, ce qui est vraiment très utile.

5. La différence entre CyclicBarrier et CountDownLatch

Deux classes qui se ressemblent quelque peu, toutes deux sous java.util.concurrent, peuvent être utilisées pour indiquer que le code s'exécute jusqu'à un certain point. La différence entre les deux est :

. (1) Après qu'un thread de CyclicBarrier s'exécute jusqu'à un certain point, le thread s'arrête jusqu'à ce que tous les threads atteignent ce point, tous les threads ne se réexécutent pas, un thread s'exécute jusqu'à un certain point. certaine valeur -1, et le fil continue de s'exécuter

(2) CyclicBarrier ne peut évoquer qu'une seule tâche, et CountDownLatch peut évoquer plusieurs tâches

(3) CyclicBarrier peut être réutilisé, mais CountDownLatch ne peut pas être réutilisé Si la valeur de comptage est 0, le CountDownLatch ne peut pas être réutilisé

.

6. Le rôle du mot clé volatile

Une question très importante que tout programmeur Java qui apprend et applique le multi-threading doit maîtriser. Le prérequis pour comprendre le rôle du mot-clé volatile est de comprendre le modèle de mémoire Java. Je ne parlerai pas ici du modèle de mémoire Java. Vous pouvez vous référer au point 31. Le mot-clé volatile a deux fonctions principales :

. (1) Le multi-threading s'articule principalement autour des deux caractéristiques de visibilité et d'atomicité. Les variables modifiées avec le mot-clé volatile assurent leur visibilité entre multi-threads, c'est-à-dire qu'à chaque fois qu'une variable volatile est lue, elle doit être la dernière Data

(2) L'exécution sous-jacente du code n'est pas aussi simple que le langage de haut niveau que nous voyons - le programme Java. Son exécution est du code Java -> bytecode -> exécute le C/C++ correspondant selon le bytecode. ->Le code C/C++ est compilé en langage assembleur-->interagit avec les circuits matériels. En réalité, afin d'obtenir de meilleures performances, la JVM peut réorganiser les instructions et certains problèmes inattendus peuvent survenir en cas de problème de multi-thread. L'utilisation de volatile réorganisera la sémantique d'interdiction, ce qui bien sûr réduit également dans une certaine mesure l'efficacité de l'exécution du code

D'un point de vue pratique, un rôle important de volatile est de se combiner avec CAS pour garantir l'atomicité. Pour plus de détails, veuillez vous référer aux classes du package java.util.concurrent.atomic, telles que AtomicInteger.

7. Qu'est-ce que la sécurité des fils

C'est une autre question théorique, et il existe de nombreuses réponses différentes. Je vais vous donner celle qui, à mon avis, l'explique le mieux : si votre code est exécuté dans plusieurs threads et exécuté dans un seul thread, vous obtiendrez toujours le même résultat. alors votre code est thread-safe.

Il convient de mentionner à propos de cette question qu'il existe plusieurs niveaux de sécurité des threads :

(1) Immuable

Les classes comme String, Integer et Long sont toutes des types finaux. Aucun thread ne peut modifier ses valeurs à moins d'en créer une nouvelle. Par conséquent, ces objets immuables peuvent être directement utilisés dans un environnement multithread sans aucun moyen de synchronisation. 🎜>

(2) Sécurité absolue des fils

Quel que soit l'environnement d'exécution, aucune mesure de synchronisation supplémentaire n'est requise par l'appelant. Pour y parvenir, vous devez généralement payer beaucoup de coûts supplémentaires. En fait, la plupart des classes Java marquées comme thread-safe ne le sont pas. Cependant, il existe également des classes Java qui sont absolument thread-safe. sûr, comme CopyOnWriteArrayList et CopyOnWriteArraySet

. (3) Sécurité relative des fils

La sécurité relative des threads est ce que nous appelons habituellement la sécurité des threads. Comme Vector, les méthodes d'ajout et de suppression sont des opérations atomiques et ne seront pas interrompues, mais elles sont limitées à cela si un thread traverse un Vector , s'il y a un thread qui l'ajoute. Dans le même temps, ConcurrentModificationException se produira dans 99 % des cas, ce qui constitue le mécanisme d'échec rapide.

(4) Le fil de discussion n'est pas sécurisé

Il n'y a rien à dire à ce sujet. ArrayList, LinkedList, HashMap, etc. sont toutes des classes non sécurisées pour les threads

.

8. Comment obtenir un fichier de vidage de thread en Java

Pour des problèmes tels que des boucles infinies, des blocages, des blocages et une ouverture de page lente, le thread dump est le meilleur moyen de résoudre le problème. Ce qu'on appelle le thread dump est la pile de threads. Il y a deux étapes pour obtenir la pile de threads :

. (1) Pour obtenir le pid du thread, vous pouvez utiliser la commande jps. Dans l'environnement Linux, vous pouvez également utiliser ps -ef | grep java

. (2) Pour imprimer la pile de threads, vous pouvez utiliser la commande jstack pid Dans l'environnement Linux, vous pouvez également utiliser kill -3 pid

. De plus, la classe Thread 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, cette méthode est donc liée à une instance de thread spécifique. Chaque fois qu'elle est obtenue, la pile actuellement exécutée par un thread spécifique est obtenue,

.

9. Que se passe-t-il si un thread rencontre une exception d'exécution

Si cette exception n'est pas interceptée, le thread cesse de s'exécuter. Un autre point important est : si ce fil contient un moniteur pour un certain objet, alors le moniteur d'objet sera immédiatement libéré

10. Comment partager des données entre deux threads

Partagez simplement des objets entre les threads, puis réveillez-vous et attendez via wait/notify/notifyAll, wait/signal/signalAll Par exemple, la file d'attente de blocage BlockingQueue est conçue pour partager des données entre les threads

.

11. Quelle est la différence entre la méthode de sommeil et la méthode d'attente

Cette question est souvent posée. La méthode sleep et la méthode wait peuvent être utilisées pour abandonner le processeur pendant un certain temps. La différence est que si le thread détient le moniteur d'un objet, la méthode sleep n'abandonnera pas. le moniteur de cet objet, et la méthode d'attente abandonnera le moniteur de l'objet

.

12. Quel est le rôle du modèle producteur-consommateur

Cette question est très théorique, mais très importante :

(1) Améliorer l'efficacité opérationnelle de l'ensemble du système en équilibrant la capacité de production des producteurs et la capacité de consommation des consommateurs. C'est le rôle le plus important du modèle producteur-consommateur

. (2) Le découplage, qui est une fonction accessoire du modèle producteur-consommateur, signifie qu'il y a moins de liens entre producteurs et consommateurs, plus ils peuvent se développer indépendamment sans contraintes mutuelles

.

13. À quoi sert ThreadLocal ?

En termes simples, ThreadLocal est une méthode d'échange d'espace contre du temps.Chaque thread maintient un ThreadLocal.ThreadLocalMap implémenté par la méthode d'adresse ouverte, qui isole les données et ne partage pas les données. Naturellement, il n'y a pas de problèmes de sécurité des threads

<.>

14. Pourquoi les méthodes wait() et notify()/notifyAll() sont-elles appelées dans un bloc synchronisé

Ceci est obligatoire par le JDK. Les méthodes wait() et notify()/notifyAll() doivent obtenir le verrou de l'objet avant d'appeler

.

15. Quelle est la différence entre la méthode wait() et la méthode notify()/notifyAll() lors de l'abandon du moniteur d'objets

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 wait() libère le moniteur d'objet immédiatement, tandis que la méthode notify()/notifyAll() attend le le code restant du thread pour terminer l’exécution abandonnera le moniteur d’objet.

16.Pourquoi utiliser le pool de threads

Évitez de créer et de détruire fréquemment des threads pour pouvoir réutiliser les objets thread. De plus, l'utilisation du pool de threads permet également de contrôler de manière flexible le nombre de concurrence en fonction du projet.

17. Comment détecter si un thread contient un moniteur d'objet

J'ai également vu une question d'entretien multi-thread sur Internet et appris qu'il existe un moyen de déterminer si un thread contient un moniteur d'objet : la classe Thread fournit une méthode holdLock(Object obj), si et seulement si le moniteur de l'objet obj est détenu par un certain thread. Il ne retournera vrai que lorsqu'il est détenu par un thread. 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 if, else, for et while, et ReentrantLock est une classe. C'est la différence essentielle entre les deux. Puisque ReentrantLock est une classe, il fournit des fonctionnalités de plus en plus flexibles que synchronisées. Il peut être hérité, peut avoir des méthodes et peut avoir diverses variables de classe. L'évolutivité de ReentrantLock par rapport à synchronisé se reflète en plusieurs points :

(1) ReentrantLock peut définir le temps d'attente pour acquérir le verrou, évitant ainsi les impasses

(2) ReentrantLock peut obtenir des informations sur différents verrous

(3) ReentrantLock peut implémenter de manière flexible plusieurs notifications

De plus, les mécanismes de verrouillage des deux sont en réalité différents. La couche inférieure de ReentrantLock appelle la méthode park de Unsafe pour se verrouiller, et synchronisée devrait fonctionner sur le mot de marque dans l'en-tête de l'objet. Je n'en suis pas sûr.

19. Quelle est la concurrence de ConcurrentHashMap

La concurrence de ConcurrentHashMap est la taille du segment. La valeur par défaut est 16, ce qui signifie que jusqu'à 16 threads peuvent exécuter ConcurrentHashMap en même temps. C'est aussi le plus grand avantage de ConcurrentHashMap par rapport à Hashtable. threads en même temps pour obtenir le contenu de Hashtable Data ?

20. Qu'est-ce que ReadWriteLock

Tout d’abord, soyons clairs, ce n’est pas que ReentrantLock soit mauvais, c’est juste que ReentrantLock a des limites dans certains cas. Si ReentrantLock est utilisé, cela peut empêcher l'incohérence des données causée par le thread A écrivant des données et le thread B lisant des données. Cependant, si le thread C lit des données et que le thread D lit également des données, la lecture des données ne modifiera pas les données. Ce n'est pas nécessaire. Il est verrouillé, mais il est toujours verrouillé, ce qui réduit les performances du programme.

C'est pour cette raison qu'est né le verrou en lecture-écriture ReadWriteLock. ReadWriteLock est une interface de verrouillage en lecture-écriture. ReentrantReadWriteLock est une implémentation spécifique de l'interface ReadWriteLock, qui réalise la séparation de la lecture et de l'écriture. Le verrou de lecture est partagé et le verrou d'écriture est exclusif. Lecture et écriture, Écriture et lecture, écriture et écriture s'excluent mutuellement, ce qui améliore les performances de lecture et d'écriture.

21. Qu'est-ce que FutureTask

En fait, comme mentionné précédemment, FutureTask représente une tâche d'opération asynchrone. Une classe d'implémentation spécifique de Callable peut être transmise à FutureTask, et des opérations telles que l'attente d'obtenir les résultats de cette tâche d'opération asynchrone, la détermination si elle a été terminée et l'annulation de la tâche peuvent être effectuées. Bien entendu, puisque FutureTask est également une classe d'implémentation de l'interface Runnable, FutureTask peut également être placée dans le pool de threads.

22. Comment trouver quel thread utilise le processeur le plus long dans un environnement Linux

C’est une question plus pratique, qui me semble tout à fait significative. Vous pouvez faire ceci :

(1) Récupérez le pid, jps ou ps -ef | grep java du projet, cela a déjà été mentionné

(2) top -H -p pid, l'ordre ne peut pas être modifié

Cela imprimera le projet en cours et le pourcentage de temps CPU pris par chaque thread. Notez que ce qui est tapé ici est LWP, qui est le numéro de thread du thread natif du système d'exploitation. Mon ordinateur portable n'a pas déployé de projets Java dans l'environnement Linux, il n'y a donc aucun moyen de prendre des captures d'écran pour une démonstration, si vous êtes un internaute. L'entreprise utilise l'environnement Linux pour déployer des projets, vous pouvez l'essayer une fois.

En utilisant "top -H -p pid" + "jps pid", vous pouvez facilement trouver la pile de threads d'un thread qui occupe une grande quantité de CPU, localisant ainsi la cause de l'occupation élevée du CPU. Cela est généralement dû à des opérations de code incorrectes. qui mènent à une boucle infinie.

Une dernière chose à mentionner, le LWP imprimé par "top -H -p pid" est en décimal, et le numéro de thread local imprimé par "jps pid" est en hexadécimal. En le convertissant, vous pouvez localiser le thread qui occupe un. beaucoup de CPU. Le thread actuel est empilé.

23. Programmation Java pour écrire un programme qui provoquera une impasse

La première fois que j'ai vu ce sujet, j'ai pensé que c'était une très bonne question. Beaucoup de gens savent ce qu'est un blocage : le thread A et le thread B attendent le verrouillage de l'autre, ce qui entraîne la poursuite du programme dans une boucle infinie. Bien sûr, cela se limite à cela. Si vous demandez comment écrire un programme de blocage, vous ne le saurez pas, pour le dire franchement, cette situation signifie que vous ne comprenez pas ce qu'est un blocage, comprenez simplement une théorie et c'est tout. En pratique, vous rencontrez des problèmes de blocage. C'est fondamentalement invisible.

Pour vraiment comprendre ce qu'est une impasse, ce problème n'est en fait pas difficile, il suffit de quelques étapes :

(1) Les deux threads contiennent respectivement deux objets Object : lock1 et lock2. Ces deux verrous servent de verrous pour les blocs de codes synchronisés

; (2) Le bloc de code de synchronisation dans la méthode run() du thread 1 acquiert d'abord le verrou d'objet de lock1, Thread.sleep(xxx Le temps n'a pas besoin d'être trop long, 50 millisecondes suffisent presque, puis acquiert). le verrou d'objet de lock2. Ceci est principalement fait pour empêcher le thread 1 d'acquérir en permanence les verrous d'objet des objets lock1 et lock2

(3) Exécution du thread 2) (Le bloc de code de synchronisation dans la méthode acquiert d'abord le verrou d'objet de lock2, puis acquiert le verrou d'objet de lock1. Bien sûr, à ce moment, le verrou d'objet de lock1 a été verrouillé par le thread 1. , et le thread 2 doit attendre le thread 1 Libérez le verrou d'objet de lock1

. De cette façon, après que le thread 1 se soit « endormi », le thread 2 a acquis le verrou d'objet de lock2. Le thread 1 tente d'acquérir le verrou d'objet de lock2 et est bloqué à ce moment-là, une impasse est formée. Je n'écrirai pas le code car il prend beaucoup de place. Java Multithreading 7 : Deadlock est inclus dans cet article, qui est l'implémentation du code des étapes ci-dessus.

24. Comment réveiller un fil de discussion bloqué

Si le thread est bloqué en raison de l'appel de la méthode wait(), sleep() ou join(), vous pouvez interrompre le thread et le réveiller en lançant InterruptedException. Si le thread rencontre un blocage d'IO, vous ne pouvez rien faire car IO ; est un système d'exploitation Implémenté, le code Java n'a aucun moyen de contacter directement le système d'exploitation.

25. Comment les objets immuables aident-ils le multithreading ?

Comme mentionné précédemment, les objets immuables garantissent la visibilité en mémoire des objets. La lecture d'objets immuables ne nécessite pas de méthodes de synchronisation supplémentaires, 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 du contrôle du processeur d'un thread déjà en cours d'exécution vers un autre thread prêt et en attente d'obtenir les droits d'exécution du processeur.

27. Que se passera-t-il si la file d'attente du pool de threads est pleine lorsque vous soumettez une tâche

Si vous utilisez LinkedBlockingQueue, qui est une file d'attente illimitée, 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 des tâches à l'infini si vous l'utilisez. une file d'attente limitée, par exemple Dans le cas d'ArrayBlockingQueue, les tâches seront d'abord ajoutées à ArrayBlockingQueue si ArrayBlockingQueue est pleine, la politique de rejet RejectedExecutionHandler sera utilisée pour traiter les tâches complètes.

28. Quel est l'algorithme de planification des threads utilisé en Java

De préemption. Une fois qu'un thread a utilisé le processeur, le système d'exploitation calculera une priorité totale en fonction de la priorité du thread, de la faim des threads et d'autres données et allouera la tranche de temps suivante à un certain thread pour l'exécution.

29. Quelle est la fonction de Thread.sleep(0)

Cette question est liée à la question ci-dessus, je les ai donc connectées ensemble. Étant donné que Java utilise un algorithme de planification préemptive des threads, il peut arriver qu'un certain thread obtienne souvent le contrôle du CPU. Afin de permettre à certains threads de priorité inférieure d'obtenir le contrôle du CPU, vous pouvez utiliser Thread.sleep( 0) pour déclencher manuellement une opération du. système d'exploitation pour allouer des tranches de temps, ce qui est également une opération pour équilibrer le contrôle du processeur.

30. Qu'est-ce que le spin

Une grande partie du code synchronisé n'est que du code très simple et le temps d'exécution est très rapide. Verrouiller tous les threads en attente à ce moment-là n'est peut-être pas une opération rentable, car le blocage des threads implique de basculer entre le mode utilisateur et le mode noyau. Étant donné que le code synchronisé s'exécute très rapidement, il est préférable d'éviter que le thread en attente du verrou soit bloqué, mais de faire une boucle occupée à la limite synchronisée. Si vous effectuez plusieurs boucles occupées et constatez que le verrou n'a pas été obtenu, bloquer à nouveau peut être une meilleure stratégie.

31. Qu'est-ce que le modèle de mémoire Java

Le modèle de mémoire Java définit une spécification pour l'accès multithread à la mémoire Java. Une explication complète du modèle de mémoire Java ne peut pas être expliquée clairement en quelques phrases. Permettez-moi de 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é dans la mémoire principale. Chaque fois que le thread Java utilise ces variables dans la mémoire principale, il lira une fois les variables dans la mémoire principale et les laissera exister. la mémoire principale. Il y a une copie dans votre propre mémoire de travail. Lorsque vous exécutez votre propre code de thread, lorsque vous utilisez ces variables, vous exploitez la copie dans votre propre mémoire de travail. Une fois le code du thread exécuté, la dernière valeur sera mise à jour dans la mémoire principale

(2) Plusieurs opérations atomiques sont définies pour les variables d'exploitation dans la mémoire principale et la mémoire de travail

(3) Définir les règles d'utilisation des variables volatiles

(4) arrive avant, c'est-à-dire le principe d'occurrence avant, définit certaines règles selon lesquelles l'opération A doit se produire avant l'opération B. Par exemple, dans le même thread, le code devant le flux de contrôle doit apparaître avant le code derrière le flux de contrôle, un déverrouillage. L'action de déverrouillage doit précéder les actions de verrouillage ultérieures pour le même verrou, etc. Tant que ces règles sont respectées, aucune mesure de synchronisation supplémentaire n'est requise. Si un morceau de code ne répond pas à tous les événements. avant les règles, alors ce code Il doit être thread-unsafe

32. Qu'est-ce que le CAS

CAS signifie Compare and Swap, ce qui signifie comparer et remplacer. Supposons qu'il y ait trois opérandes : la valeur mémoire V, l'ancienne valeur attendue A et la valeur à modifier B. Si et seulement si la valeur attendue A et la valeur mémoire V sont identiques, la valeur mémoire sera modifiée en B et true sera être retourné. Sinon, quoi faire ni et retourner faux. Bien entendu, CAS doit coopérer avec des variables volatiles pour garantir que la variable obtenue à chaque fois est la dernière valeur dans la mémoire principale, sinon l'ancienne valeur attendue A sera toujours une valeur inchangée A pour un certain thread. L’opération CAS échoue, elle ne réussira jamais.

33. Que sont les verrous optimistes et les verrous pessimistes

(1) Verrouillage optimiste : tout comme son nom, il est optimiste quant aux problèmes de sécurité des threads causés par les opérations simultanées. Il estime que la concurrence n'aura pas toujours lieu, il n'a donc pas besoin de maintenir le verrou, et les comparera et les remplacera. 2. En tant qu'opération atomique, cette action tente de modifier les variables dans la mémoire. Si elle échoue, cela signifie qu'un conflit se produit et qu'il devrait y avoir une logique de nouvelle tentative correspondante.

(2) Verrouillage pessimiste : comme son nom, il est pessimiste quant aux problèmes de sécurité des threads causés par les opérations simultanées. Le verrouillage pessimiste estime que la concurrence se produira toujours, donc chaque fois qu'une ressource est exploitée, un verrou exclusif est comme synchronisé. ce qui se passe, vous pouvez directement exploiter la ressource après l'avoir verrouillée.

34. Qu'est-ce que l'AQS

Parlons brièvement d'AQS. Le nom complet d'AQS est AbstractQueuedSychronizer, qui devrait être traduit par synchroniseur de file d'attente abstraite.

Si la base de java.util.concurrent est CAS, alors AQS est le cœur de l'ensemble du package de concurrence Java. ReentrantLock, CountDownLatch, Semaphore, etc. l'utilisent tous. AQS connecte en fait toutes les entrées sous la forme d'une file d'attente bidirectionnelle, telle que ReentrantLock. Tous les threads en attente sont placés dans une entrée et connectés à une file d'attente bidirectionnelle. Si le thread précédent utilise ReentrantLock, la file d'attente bidirectionnelle est en fait la première entrée qui démarre.

AQS définit toutes les opérations sur les files d'attente bidirectionnelles, mais ouvre uniquement les méthodes tryLock et tryRelease aux développeurs. Les développeurs peuvent réécrire les méthodes tryLock et tryRelease selon leur propre implémentation pour réaliser leurs propres fonctions de concurrence.

35. Sécurité des threads du mode singleton

C’est une question banale. La première chose à dire est que la sécurité des threads du mode singleton signifie qu’une instance d’une certaine classe ne sera créée 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) Comment écrire le modèle singleton de style Hungry : sécurité des fils

(2) Comment écrire le modèle singleton paresseux : non thread-safe

(3) Comment écrire le mode singleton de verrouillage à double vérification : sécurité des threads

36. Quelle est la fonction du Sémaphore ?

Le sémaphore est un sémaphore, sa fonction est de limiter le nombre de concurrence d'un certain bloc de code. Le sémaphore a un constructeur qui peut transmettre un entier n, ce qui signifie qu'un certain morceau de code n'est accessible que par n threads au maximum. Si n est dépassé, veuillez attendre qu'un thread ait fini d'exécuter ce bloc de code, puis le thread suivant. Entrez à nouveau. On peut voir de là que si le type int entier n=1 passé dans le constructeur Sémaphore équivaut à devenir synchronisé.

37. Il n'y a qu'une seule instruction « return count » dans la méthode size() de Hashtable. Pourquoi doit-elle être synchronisée ?

C'était une confusion que j'avais auparavant. Je me demande si quelqu'un a pensé à ce problème. S'il y a plusieurs instructions dans une méthode et qu'elles fonctionnent toutes sur la même variable de classe, alors ne pas verrouiller dans un environnement multithread entraînera inévitablement des problèmes de sécurité des threads. C'est facile à comprendre, mais la méthode size() en a clairement. une seule déclaration, pourquoi devrions-nous la verrouiller ?

Concernant cette question, j'ai progressivement acquis une compréhension en travaillant et en étudiant. Il y a deux raisons principales :

(1) Un seul thread peut exécuter la méthode synchronisée d'une classe fixe en même temps, mais la méthode asynchrone de la classe est accessible par plusieurs threads en même temps. Par conséquent, il y a un problème. Peut-être que le thread A exécute la méthode put de Hashtable pour ajouter des données, et le thread B peut appeler la méthode size() normalement pour lire le nombre d'éléments actuels dans la table de hachage, et la valeur lue peut ne pas l'être. le dernier. , peut-être que le thread A a ajouté les données, mais le thread B a déjà lu la taille sans corriger size++, donc la taille lue par le thread B doit être inexacte. Après avoir ajouté la synchronisation à la méthode size(), cela signifie que le thread B ne peut appeler la méthode size() qu'une fois que le thread A a fini d'appeler la méthode put, garantissant ainsi la sécurité des threads

(2) Le processeur exécute du code, pas du code Java. Ceci est très important et doit être rappelé. Le code Java est finalement traduit en code assembleur pour exécution. Le code assembleur est le code qui peut réellement interagir avec les circuits matériels. Même si vous voyez qu'il n'y a qu'une seule ligne de code Java, ou même si vous voyez que le bytecode généré après la compilation du code Java n'est qu'une seule ligne, cela ne signifie pas que pour la couche inférieure, cette instruction n'a qu'une seule opération. En supposant que la phrase « nombre de retours » soit traduite en trois instructions d'assemblage pour l'exécution, il est tout à fait possible que le thread soit commuté après l'exécution de la première phrase.

38. Par quel thread le constructeur de classe de thread et le bloc statique sont-ils appelés ? C'est une question très délicate et astucieuse. N'oubliez pas : la méthode de construction 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 run est appelé par le thread lui-même.

Si l'instruction ci-dessus vous déroute, laissez-moi vous donner un exemple. Supposons que Thread1 soit nouveau dans Thread2 et que Thread2 soit nouveau dans la fonction principale, alors :

(1) La méthode de construction 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) La méthode de construction 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, méthode de synchronisation ou bloc de synchronisation

Bloc synchronisé, ce qui signifie que le code en dehors du bloc synchronisé est exécuté de manière asynchrone, ce qui améliore l'efficacité du code par rapport à la synchronisation de l'ensemble de la méthode. Veuillez connaître une règle : plus la portée de la synchronisation est petite, mieux c'est.

Dans cet esprit, je voudrais mentionner que même si plus la portée de la synchronisation est petite, mieux c'est, il existe toujours une méthode d'optimisation appelée grossissement des verrous dans la machine virtuelle Java, qui consiste à élargir la portée de la synchronisation. Ceci est utile, par exemple, StringBuffer est une classe thread-safe. Naturellement, la méthode append() la plus couramment utilisée est une méthode de synchronisation. Lorsque nous écrivons du code, nous ajouterons la chaîne à plusieurs reprises, ce qui signifie un verrouillage répété -> Déverrouillage. , ce qui est mauvais pour les performances, car cela signifie que la machine virtuelle Java doit basculer à plusieurs reprises entre le mode noyau et le mode utilisateur sur ce thread, de sorte que la machine virtuelle Java traitera le code qui appelle plusieurs méthodes d'ajout. L'opération de grossissement du verrouillage en étend plusieurs. ajoute des opérations au début et à la fin de la méthode d'ajout et la transforme en un grand bloc de synchronisation. Cela réduit le nombre de verrous et de déverrouillages et améliore efficacement l'efficacité de l'exécution du code.

40. Comment utiliser le pool de threads pour les entreprises avec une concurrence élevée et un temps d'exécution de tâches court ? Comment utiliser le pool de threads pour les entreprises avec une faible concurrence et un temps d'exécution des tâches long ? Comment utiliser le pool de threads pour les entreprises avec une concurrence élevée et un temps d'exécution long ?

C'est une question que j'ai vue sur Internet de programmation concurrente. J'ai posé cette question lors de la dernière fois. J'espère que tout le monde pourra la voir et y réfléchir, car cette question est très bonne, très pratique et très professionnelle. Concernant cette question, mon opinion personnelle est la suivante :

(1) Pour les entreprises avec une concurrence élevée et un temps d'exécution des tâches court, le nombre de threads du pool de threads peut être défini sur le nombre de cœurs de processeur + 1 pour réduire le changement de contexte de thread

(2) Il convient de distinguer les entreprises à faible simultanéité et à long temps d'exécution des tâches :

a) Si le temps d'activité est concentré pendant une longue période sur les opérations d'E/S, c'est-à-dire les tâches gourmandes en E/S, car les opérations d'E/S n'occupent pas le processeur, ne laissez donc pas tous les processeurs inactifs, vous pouvez augmenter le nombre de threads dans le pool de threads, pour que le CPU gère plus d'affaires

b) Si le temps de travail est concentré pendant une longue période sur les opérations informatiques, c'est-à-dire les tâches gourmandes en calcul, il n'y a aucun moyen de le faire. C'est la même chose que (1). Le nombre de threads dans le pool de threads est égal à (1). réglé pour être plus petit pour réduire le changement de contexte de thread

(3) Une concurrence élevée et un temps d'exécution commercial long. La clé pour résoudre ce type de tâche ne réside pas dans le pool de threads mais dans la conception de l'architecture globale. Vérifier si certaines données de ces entreprises peuvent être mises en cache est la première étape. l'ajout de serveurs est la troisième étape. Dans la deuxième étape, en ce qui concerne les paramètres du pool de threads, veuillez vous référer à (2) pour les paramètres. Enfin, le problème des longs délais d’exécution des activités devra peut-être également être analysé pour voir si un middleware peut être utilisé pour diviser et découpler les tâches.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn