Les verrous d'objet sont utilisés pour les méthodes d'instance d'objet, ou une instance d'objet, et les verrous de classe sont utilisés pour les méthodes statiques d'une classe ou les objets de classe d'une classe. Nous savons qu'il peut y avoir plusieurs instances d'objet d'une classe, mais chaque classe n'a qu'un seul objet de classe, donc les verrous d'objet des différentes instances d'objet n'interfèrent pas les uns avec les autres, mais il n'y a qu'un seul verrou de classe pour chaque classe.
Une chose à noter est qu'en fait, le verrouillage de classe n'est qu'une chose conceptuelle et n'existe pas réellement. Le verrouillage de classe verrouille réellement l'objet de classe correspondant à chaque classe. Les verrous de classe et les verrous d’objet n’interfèrent pas non plus les uns avec les autres.
La visibilité signifie que lorsque plusieurs threads accèdent à la même variable, si un thread modifie la valeur de la variable, les autres threads peuvent immédiatement voir la valeur modifiée.
Puisque toutes les opérations sur les variables par les threads doivent être effectuées dans la mémoire de travail et ne peuvent pas directement lire et écrire des variables dans la mémoire principale, alors pour les variables partagées V, elles sont d'abord dans leur propre mémoire de travail puis synchronisées avec la mémoire principale. Cependant, il ne sera pas vidé dans la mémoire principale à temps, mais il y aura un certain décalage horaire. Évidemment, à ce stade, l'opération du thread A sur la variable V n'est plus visible pour le thread B.
Pour résoudre le problème de visibilité des objets partagés, on peut utiliser le mot clé volatile ou le verrou.
Les processeurs actuels supportent essentiellement l'instruction CAS(), mais les algorithmes implémentés par chaque fabricant sont différents. Chaque processus de fonctionnement CAS contient trois opérateurs : une adresse mémoire V, une valeur attendue A et une nouvelle valeur B. Pendant le fonctionnement. , si la valeur stockée à cette adresse est égale à la valeur attendue A, alors la valeur à l'adresse est affectée à la nouvelle valeur B, sinon aucune opération n'est effectuée.
L'idée de base de CAS est que si la valeur à cette adresse est égale à la valeur attendue, donnez-lui une nouvelle valeur. Sinon, ne faites que renvoyer la valeur d'origine. Loop CAS consiste à effectuer en continu des opérations cas dans une boucle jusqu'à ce qu'elle réussisse. On peut également parler de trois problèmes majeurs avec CAS.
Un thread peut entrer à plusieurs reprises dans n'importe quel bloc de code synchronisé par un verrou qu'il possède déjà synchronisé et ReentrantLock sont tous deux des verrous réentrants. En termes d'implémentation, chaque fois qu'un thread acquiert un verrou, il détermine si le thread qui acquiert le verrou est lui-même et accumule simplement le compteur. Chaque fois que le verrou est libéré, le compteur est décrémenté jusqu'à ce que la calculatrice revienne à zéro, indiquant que. le fil a été complètement libéré. La couche inférieure est implémentée à l'aide d'AQS dans JUC.
est le cadre de base utilisé pour créer des verrous ou d'autres composants de synchronisation, tels que ReentrantLock, ReentrantReadWriteLock et CountDownLatch, qui sont implémentés sur la base d'AQS. Il utilise une variable membre int pour représenter l'état de synchronisation et termine le travail de mise en file d'attente des threads d'acquisition de ressources via la file d'attente FIFO intégrée. Il s'agit d'une variante d'implémentation du verrou de file d'attente CLH. Il peut réaliser 2 méthodes de synchronisation : exclusive et partagée.
La principale façon d'utiliser AQS est l'héritage. Les sous-classes gèrent l'état de synchronisation en héritant d'AQS et en implémentant ses méthodes abstraites. La conception du synchroniseur est basée sur le modèle de méthode modèle, donc si nous voulons implémenter notre propre classe d'outils de synchronisation, nous en avons besoin. pour couvrir plusieurs d'entre elles, telles que tryAcquire, tryReleaseShared, etc.
Le but de cette conception est que les composants de synchronisation (tels que les verrous) sont orientés utilisateur. Elle définit l'interface permettant aux utilisateurs d'interagir avec les composants de synchronisation (par exemple, en permettant à deux threads d'accéder en parallèle), en masquant les détails d'implémentation du synchroniseur ; est pour L'implémenteur de verrous simplifie la mise en œuvre des verrous et protège les opérations sous-jacentes telles que la gestion de l'état de synchronisation, la mise en file d'attente des threads, l'attente et le réveil. Cela isole efficacement les domaines sur lesquels les utilisateurs et les responsables de la mise en œuvre doivent se concentrer.
En interne, AQS maintient un état de ressource partagé et utilise le FIFO intégré pour terminer le travail de mise en file d'attente des threads d'acquisition de ressources. La file d'attente est composée de nœuds Node un par un. Chaque nœud Node conserve une référence précédente et une référence suivante, qui pointent respectivement vers ses propres nœuds prédécesseur et successeur, formant une liste doublement liée à deux extrémités.
principe de (ce) synchronisation : implique deux instructions : Monitorenter, Monitorexit ; parlons de la méthode de synchronisation. À en juger par les résultats de décompilation de la méthode de synchronisation, la synchronisation de la méthode n'est pas obtenue via les instructions Monitorenter et Monitorexit. la méthode ordinaire, son Il existe un identifiant ACC_SYNCHRONIZED supplémentaire dans le pool de constantes.
La JVM implémente la synchronisation des méthodes basée sur cet identifiant : lorsque la méthode est appelée, l'instruction appelante vérifiera si l'indicateur d'accès ACC_SYNCHRONIZED de la méthode est défini. S'il est défini, le thread d'exécution obtiendra d'abord le moniteur, puis s'exécutera. une fois l'acquisition réussie. Corps de la méthode, le moniteur est libéré une fois la méthode exécutée. Pendant l’exécution de la méthode, aucun autre thread ne peut à nouveau obtenir le même objet moniteur.
Introduction de technologies telles que le verrouillage par rotation, le verrouillage par rotation adaptatif, l'élimination du verrouillage, le grossissement du verrouillage, le verrouillage biaisé, le verrouillage léger, l'analyse d'échappement et d'autres technologies pour réduire le coût des opérations de verrouillage.
Escape Analysis : S'il est prouvé qu'un objet ne s'échappera pas d'une méthode ou d'un thread, cette variable peut être optimisée :
Synchronization Elimination : synchronisation Elimination, si un objet ne s'échappera pas un thread , alors les mesures de synchronisation pour cette variable peuvent être éliminées.
Élimination des verrous et élimination grossière des verrous : Si le compilateur d'exécution de la machine virtuelle détecte qu'il est peu probable qu'une concurrence de données partagées se produise sur un code nécessitant une synchronisation au moment de l'exécution, il supprimera ces verrous.
Verrouillage grossier : fusionnez les blocs de code adjacents avec le même verrou. L'élimination de l'acquisition et de la libération de verrous inutiles peut améliorer les performances d'exécution du programme.
Les verrous d'objet sont utilisés sur les méthodes d'instance d'objet ou sur une instance d'objet. Les verrous de classe sont utilisés sur les méthodes statiques d'une classe ou sur les objets de classe d'une classe. Nous savons qu'il peut y avoir plusieurs instances d'objet d'une classe, mais chaque classe n'a qu'un seul objet de classe, donc les verrous d'objet des différentes instances d'objet n'interfèrent pas les uns avec les autres, mais il n'y a qu'un seul verrou de classe pour chaque classe.
Une chose à noter est qu'en fait, le verrouillage de classe n'est qu'une chose conceptuelle et n'existe pas réellement. Le verrouillage de classe verrouille réellement l'objet de classe correspondant à chaque classe. Les verrous de classe et les verrous d’objet n’interfèrent pas non plus les uns avec les autres.
Il n'y a aucune garantie que le rôle de DCL soit : volatile assurera la visibilité et l'ordre des variables modifiées, garantissant qu'en mode singleton, l'ordre d'exécution lors de la création d'un objet doit être
allocation d'espace mémoire
Instanciez l'instance d'objet
Pointez la référence de l'instance vers l'espace mémoire alloué. À ce moment, l'instance a une adresse mémoire et n'est plus nulle. Cela garantit que l'instance est nulle ou déjà un objet entièrement initialisé.
volatile est le mécanisme de synchronisation le plus léger. Volatile garantit la visibilité lorsque différents threads opèrent sur cette variable. Autrement dit, si un thread modifie la valeur d'une variable, la nouvelle valeur est immédiatement visible par les autres threads. Cependant, volatile ne peut pas garantir l'atomicité des opérations, donc les opérations d'écriture composites sous multi-threads entraîneront des problèmes de sécurité des threads.
Le mot-clé synchronisé peut être utilisé pour modifier des méthodes ou sous forme de blocs synchronisés. Il garantit principalement que plusieurs threads ne peuvent avoir qu'un seul thread dans une méthode ou un bloc synchronisé en même temps. Il assure la visibilité de l'accès des threads aux variables. et exclusivité, également connu sous le nom de mécanismes de verrouillage intégrés.
Le thread Daemon est un thread de support car il est principalement utilisé pour la planification en arrière-plan et le travail de support dans le programme. Cela signifie que lorsqu'il n'y a aucun thread non démon dans une machine virtuelle Java, la machine virtuelle Java se ferme. Un thread peut être défini comme thread démon en appelant Thread.setDaemon (true). Nous ne l'utilisons généralement pas. Par exemple, le thread de récupération de place est le thread Daemon.
Suspension du fil de discussion : soit l'exécution de l'exécution est terminée, soit une exception non gérée est levée, entraînant la fin prématurée du fil de discussion. Les API correspondant au thread Thread pour suspendre, reprendre et arrêter les opérations sont suspend(), Ensure() et stop(). Cependant, ces API sont obsolètes et leur utilisation n’est pas recommandée. Parce que cela entraînera le fonctionnement du programme dans un état incertain.其他 La suspension de la sécurité est que d'autres threads interrompent l'opération d'interruption en appelant la méthode interruption () d'un thread A, et le thread interrompu doit déterminer s'il est interrompu par le thread via le thread par la méthode Thread.interrupted(. ) est utilisé pour déterminer si le thread actuel est interrompu, mais Thread.interrupted() réécrira également le bit de l'indicateur d'interruption sur false.
Méthode yield() : fait que le thread actuel abandonne la propriété du processeur, mais le moment de l'abandon ne peut pas être défini. La ressource de verrouillage ne sera pas libérée. Tous les threads exécutant rendement() peuvent être à nouveau sélectionnés par le système d'exploitation et exécutés immédiatement après être entrés dans l'état prêt.
Après l'appel de rendement() et sleep(), le verrou détenu par le thread actuel ne sera pas libéré.
Après avoir appelé la méthode wait(), le verrou détenu par le thread actuel sera libéré, et une fois le thread actuel réveillé, il entrera à nouveau en compétition pour le verrou. Le code derrière la méthode wait ne sera exécuté qu'une fois le verrou terminé. concouru.
Wait est généralement utilisé pour l'interaction entre les threads, sleep est généralement utilisé pour suspendre l'exécution et la méthode rendement() amène le thread actuel à abandonner la propriété du processeur.
Le fil d'attente utilise notify/notifyAll() pour se réveiller.
sleep lui-même prend en charge les interruptions. Si le thread est interrompu pendant le sommeil, une exception d'interruption sera levée.
Le statut des threads en Java est divisé en 6 types :
Initial (NOUVEAU) : Un nouvel objet thread est créé, mais la méthode start() n'a pas encore été appelée.
Exécuter (RUNNABLE) : Dans les threads Java, les deux états prêt et en cours d'exécution sont généralement appelés "en cours d'exécution". Une fois l'objet thread créé, d'autres threads (tels que le thread principal) appellent la méthode start() de l'objet. Le thread dans cet état se trouve dans le pool de threads exécutables, en attente d'être sélectionné par la planification des threads pour obtenir le droit d'utiliser le CPU. Il est actuellement dans l'état prêt. Le thread à l'état prêt passe à l'état d'exécution (running) après avoir obtenu la tranche de temps CPU.
BLOCKED : Indique que le fil est bloqué dans le verrou.
Waiting (WAITING) : Le thread entrant dans cet état doit attendre que d'autres threads effectuent certaines actions spécifiques (notification ou interruption).
Timeout wait (TIMED_WAITING) : Cet état est différent de WAITING, il peut revenir tout seul après le temps spécifié.
TERMINATED : indique que le thread a terminé son exécution.
ThreadLocal est une variable spéciale en Java. ThreadLocal fournit une copie de la variable pour chaque thread, afin que chaque thread n'accède pas au même objet à un certain moment, isolant ainsi le partage de données par plusieurs threads.
En termes d'implémentation interne, chaque thread contient un ThreadLocalMap, qui est utilisé pour enregistrer une copie des variables appartenant à chaque thread.
Pendant le processus de développement, l'utilisation rationnelle des pools de threads peut apporter 3 avantages.
Premièrement : réduire la consommation de ressources.
Deuxièmement : Améliorez la vitesse de réponse.
Troisièmement : Améliorez la gestion des threads.
Si les threads en cours d'exécution sont inférieurs à corePoolSize, créez un nouveau thread pour effectuer la tâche (notez que l'exécution de cette étape nécessite l'acquisition d'un verrou global).
Si les threads en cours d'exécution sont égaux ou supérieurs à corePoolSize, ajoutez la tâche à BlockingQueue.
Si la tâche ne peut pas être ajoutée à BlockingQueue (la file d'attente est pleine), créez un nouveau fil pour traiter la tâche.
Si la création d'un nouveau thread entraîne un dépassement du thread en cours d'exécution maximumPoolSize, la tâche sera rejetée et la méthode RejectedExecutionHandler.rejectedExecution() sera appelée.
Peut être implémenté en utilisant la méthode join.
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!