1. Analyse de code source de classe dangereuse
La classe dangereuse dans le package RT.JAR de JDK fournit des opérations atomiques au niveau matériel. Les méthodes de dangereuses sont toutes des méthodes natives, et les bibliothèques d'implémentation C ++ locales sont accessibles en utilisant JNI.
Explication des principales fonctions de la classe dangereuse dans Rt.Jar. La classe dangereuse fournit des opérations atomiques au niveau matériel et peut fonctionner directement en toute sécurité des variables de mémoire. Il est largement utilisé dans le code source du juc. Comprendre ses principes jette la base de l'étude du code source du juc.
Tout d'abord, comprenons l'utilisation des principales méthodes dans la classe dangereuse, comme suit:
1.Long ObjectFieldOffSet (champ de champ) Méthode: Renvoie l'adresse de décalage de mémoire de la variable spécifiée dans la classe à laquelle il appartient. L'adresse de décalage n'est utilisée que lors de l'accès au champ spécifié dans la fonction dangereuse. Le code suivant utilise dangereux pour obtenir le décalage de mémoire de la valeur de la variable dans ATOMICLONG dans l'objet atomiclong. Le code est le suivant:
statique {try {valuoffset = unsetafe.objectFieldOffSet (atomiclong.class.getDeclaredField ("Value")); } catch (exception ex) {lance une nouvelle erreur (ex); }}2.Int Méthode ArrayBaseOffSet (classe ArrayClass): Obtenez l'adresse du premier élément dans le tableau
3.Int Méthode ArrayIndexScale (classe ArrayClass): Obtenez le nombre d'octets occupés par un seul élément dans le tableau
3.Boolean ComparandSwapLong (objet obj, décalage long, attente longue, longue mise à jour): comparer si la valeur de la variable du décalage décalé dans l'objet obj est égale à attendre. S'il est égal, il est mis à jour avec la valeur de mise à jour, puis renvoie True, sinon il reviendra faux.
4.Publique Native GetLongvolative (objet Obj, décalage long) Méthode: Obtenez la valeur de la sémantique de mémoire volatile correspondant à la variable du décalage décalé dans Object Obj.
5.Void putOrderEdLong (objet obj, décalage long, valeur longue) Méthode: définissez la valeur du champ long correspondant à l'adresse de décalage de décalage dans l'objet OBJ à la valeur. Il s'agit de la méthode putLongvolatile avec retard, et ne garantit pas que la modification de la valeur sera immédiatement visible par d'autres threads. Les variables ne sont utiles que si elles sont modifiées avec volatils et devraient être modifiées de manière inattendue.
6. VOIDE PARK (Boolean Isabsolute, Long Time) Méthode: Bloquer le fil actuel. Lorsque le paramètre Isabsolute est égal à Faux, le temps égal à 0 signifie bloquer tout le temps. Le temps supérieur à 0 signifie que le fil de blocage sera éveillé après avoir attendu l'heure spécifiée. Cette fois est une valeur relative, une valeur incrémentielle, c'est-à-dire que le thread actuel sera éveillé après avoir accumulé le temps par rapport à l'heure actuelle. Si Isabsolute est égal à True et que le temps est supérieur à 0, cela signifie qu'il sera éveillé après avoir bloqué au point de temps spécifié. Ici, le temps est un moment absolu, qui est la valeur convertie en MS à un certain moment. De plus, lorsque d'autres threads appellent la méthode d'interruption du thread de blocage actuel et interrompent le thread actuel, le thread actuel reviendra également. Lorsque d'autres threads appellent la méthode UNDARK et prennent le thread actuel en tant que paramètre, le thread actuel reviendra également.
7. Méthode VOID UNDARK (Thread d'objet): Réveillez le fil de blocage après l'appel Park, et les paramètres sont les threads qui doivent être réveillés.
Plusieurs nouvelles méthodes ont été ajoutées à JDK1.8. Voici une liste simple des méthodes de fonctionnement du type long comme suit:
8.Long GetAndSetLong (objet Obj, décalage long, mise à jour longue) Méthode: Obtenez la valeur de la sémantique volatile variable avec décalage dans Object Obj et définissez la valeur de la sémantique volatile variable à la mise à jour. La méthode d'utilisation est la suivante:
public final long getAndSetLong (objet obj, décalage long, longue mise à jour) {long l; do {l = getLongVolatile (obj, offset); // (1)} while (! CompareAndSwapLong (obj, offset, l, update)); retour l; }À partir du code interne (1), vous pouvez utiliser GetLongVolative pour obtenir la valeur de la variable actuelle, puis utiliser le fonctionnement CAS Atomic pour définir la nouvelle valeur. Ici, en utilisant While Loops prend en compte la situation où plusieurs threads s'appellent en même temps, puis Spin-Retry est requis après l'échec du CAS.
9.Long GetAndAddLong (objet OBJ, décalage long, long addValue) Méthode: Obtenez la valeur de la sémantique de la variable volatile avec décalage dans Object Obj et définissez la valeur variable sur la valeur d'origine + addValue. La méthode d'utilisation est la suivante:
public final long getandaddlong (objet obj, décalage long, long addvalue) {long l; do {l = getLongVolatile (obj, offset); } while (! CompareAndSwapLong (obj, offset, l, l + addValue)); retour l; }Semblable à l'implémentation de GetandSetLong, sauf que lors de l'utilisation de CAS ici, la valeur d'origine + la valeur du paramètre incrémental transféré addValue est utilisée.
Alors, comment utiliser la classe dangereuse?
Voir que dangereux est tellement génial, voulez-vous vraiment vous entraîner? D'accord, regardons d'abord le code suivant:
Package com.hjc; importer Sun.Misc.unsafe; / ** * Créé par Cong le 2018/6/6. * / classe publique TESTUNSAFE {// Obtenez l'instance de dangereuse (2.2.1) Final statique UnsAvet UnsApE = unsetafe.getUnSafe (); // Enregistrez la valeur de décalage de l'état de variable dans la classe TESTUNSAFE (2.2.2) State Long StateOffset statique statique; // variable (2.2.3) État long volatile privé = 0; static {try {// Obtenez la valeur de décalage de la variable d'état dans la classe TESTUNSAFE (2.2.4) StateOffset = USAPE.ObjectFieldOffset (TESTUNSAFE.CLASS.GETDECLARGELFIELD ("State")); } catch (exception ex) {System.out.println (ex.getLocalizedMessage ()); lancer une nouvelle erreur (ex); }} public static void main (String [] args) {// Créer une instance et définir la valeur d'état sur 1 (2.2.5) TESTUNSAFE TEST = new TestUnSafe (); //(2.2.6) Boolean Success = dangeta.CompaEndSwapint (test, StateOffset, 0, 1); System.out.println (Success); }}Le code (2.2.1) obtient une instance de dangereuse, et le code (2.2.3) crée un état de variable initialisé à 0.
Le code (2.2.4) utilise unpape.ObjectFieldOffset pour obtenir l'adresse de décalage de mémoire de la variable d'état dans la classe TESTUNSAFE dans l'objet TESTUNSAFE et l'enregistrer dans la variable StateOffset.
Le code (2.2.6) appelle la méthode ComparandSwapint de l'instance dangereuse créée et définit la valeur de la variable d'état de l'objet de test. Plus précisément, si la variable d'état dont le décalage de mémoire de l'objet de test est StateOffset est 0, la valeur de mise à jour est modifiée à 1.
Nous voulons entrer true dans le code ci-dessus, mais après exécution, les résultats suivants seront sortis:
Pourquoi cela se produit-il? Vous devez saisir le code getunsafe, tel que voir ce qui est fait dedans:
Final statique privé theunsafe = new unpae (); public static dangeta getunsafe () {// (2.2.7) class localClass = Reflection.getCallerClass (); // (2.2.8) if (! Vm.IssystemDomainLoader (localClass.getClassloader ())) {Throw New SecurityException ("Usare"); } return theunsafe;} // juger si paramclassloader est un chargeur de classe bootstrap (2.2.9) public static boolean issystemDomAniader (classloader paramclassloader) {return paramclassloader == null; }Le code (2.2.7) obtient l'objet de classe de l'objet qui appelle getunsafe, voici TESTUNSAFE.cals.
Le code (2.2.8) détermine s'il s'agit de la classe locale chargée par le chargeur de classe bootstrap. La clé ici est de savoir si le chargeur bootstrap charge TESTUNSAFE.class. Ceux qui ont vu le mécanisme de chargement des classes de la machine virtuelle Java voient clairement que c'est parce que TestUnsafe.class est chargé à l'aide d'AppClassloader, donc une exception est directement lancée ici.
La question est donc de savoir pourquoi avez-vous besoin de porter ce jugement?
Nous savons que la classe dangereuse est fournie dans RT.JAR, et la classe dans RT.Jar est chargée à l'aide du chargeur de classe bootstrap. La classe où nous démarrons la fonction principale est chargée à l'aide d'AppClassloadher, donc lors du chargement de la classe dangereuse dans la fonction principale, considérant que le mécanisme de délégation parent déléguera à bootstrap pour charger la classe dangereuse.
S'il n'y a pas d'authentification du code (2.2.8), notre application peut utiliser dangereuse pour faire les choses à volonté. La classe dangereuse peut faire fonctionner directement la mémoire, ce qui est très dangereux. Par conséquent, l'équipe de développement JDK a spécialement fait cette restriction, ne permettant pas aux développeurs d'utiliser une classe dangereuse sous des canaux réguliers, mais utilisent des fonctions dangereuses dans la classe de base de Rt.Jar.
La question est, que devons-nous faire si nous voulons vraiment instancier la classe dangereuse et utiliser la fonction dangereuse?
Nous ne devons pas oublier la technologie noire de la réflexion et utiliser la réflexion universelle pour obtenir la méthode d'instance de dangereuse. Le code est le suivant:
package com.hjc; importat sun.misc.unsafe; import java.lang.reflect.field; / ** * créé par Cong le 2018/6/6. * / classe publique TESTUNSAFE {Finale statique dangereuse dangereuse; Final statique Long StateOffset; État long volatil privé = 0; statique {try {// reflète pour obtenir le champ de champ membre theunsafe theunSafe (2.2.10) champ de champ = unsetafe.class.getDeclaredField ("theunsafe"); // Défini sur Field Accessable (2.2.11 ).SetAccessible (true); // obtient la valeur de cette variable (2.2.12) disap = (dangeret) field.get (null); // obtient le décalage de l'état dans TESTUNSAFE (2.2.13) StateOffset = unpare.ObjectFieldOffset (TESTUNSAFE.class.getDeclaredField ("State")); } catch (exception ex) {System.out.println (ex.getLocalizedMessage ()); lancer une nouvelle erreur (ex); }} public static void main (String [] args) {TESTUNSAFE test = new TestUnSafe (); Boolean Success = Usare.CompareAndSwapint (test, StateOffset, 0, 1); System.out.println (Success); }}Si le code ci-dessus (2.2.10 2.2.11 2.2.12) reflète l'exemple de dangereux, le résultat en cours d'exécution est le suivant:
2. Recherche sur le code source de la classe de verrouillage
Locksupport dans Rt.Jar dans JDK est une classe d'outils, et sa fonction principale est de suspendre et de réveiller les threads. C'est la base de la création de verrous et d'autres classes de synchronisation.
La classe Locksupport sera associée à chaque thread qui l'utilise. Le thread qui appelle la méthode de la classe Locksupport par défaut ne contient pas de licence. Locksupport est implémenté en interne à l'aide de la classe dangereuse.
Ici, nous devons prêter attention à plusieurs fonctions importantes de Locksupport, comme suit:
1.Void Park () Méthode: Si le fil d'appel de fil () a obtenu la licence associée à Locksupport, alors l'appel Locksupport.Park () reviendra immédiatement. Sinon, le fil d'appel sera interdit de participer à la planification du fil, c'est-à-dire qu'il sera bloqué et suspendu. Le code suivant est l'exemple suivant:
package com.hjc; import java.util.concurrent.locks.locksupport; / ** * créé par Cong le 2018/6/6. * / classe publique LocksUpportTest {public static void main (String [] args) {System.out.println ("Park Start!"); Locksupport.park (); System.out.println ("Park Stop!"); }}Comme indiqué dans le code ci-dessus, appelez directement la méthode du parc dans la fonction principale, et le résultat final ne fera que le démarrage du parc! Ensuite, le thread actuel sera suspendu, car le thread d'appel ne contient pas de licence par défaut. Les résultats de l'opération sont les suivants:
Lorsque vous voyez d'autres threads appeler la méthode UNDARK (Thread Thread) et que le thread actuel est utilisé comme paramètre, le thread qui appelle la méthode du parc reviendra. De plus, d'autres threads appellent la méthode Interrupt () du fil de blocage. Lorsque l'indicateur d'interruption est défini ou que le fil de blocage reviendra après le faux réveil du fil, il est préférable d'utiliser des conditions de boucle pour juger.
Il convient de noter que le fil qui appelle la méthode Park () bloquée est interrompu par d'autres threads et que le filetage bloqué renvoie, ne lancera pas une exception InterruptedException.
2. Méthode VOID UNDARK (Thread Thread) Lorsqu'un thread appelle Unpark, si le thread de thread de paramètre ne maintient pas la licence associée au thread et à la classe Locksupport, laissez le thread le maintenir. Si le fil appelé Park () est suspendu avant et que le fil est appelé, le fil sera éveillé après l'appel.
Si le fil n'a pas appelé Park auparavant, après avoir appelé la méthode Unstark, la méthode Park () sera renvoyée immédiatement. Le code ci-dessus est modifié comme suit:
package com.hjc; import java.util.concurrent.locks.locksupport; / ** * créé par Cong le 2018/6/6. * / classe publique LocksUpportTest {public static void main (String [] args) {System.out.println ("Park Start!"); // oblige le thread actuel à obtenir la licence Locksupport.Unpark (Thread.currentThread ()); // Appelez à nouveau Park Locksupport.Park (); System.out.println ("Park Stop!"); }}Les résultats de l'opération sont les suivants:
Ensuite, nous envisageons un exemple pour approfondir notre compréhension du parc, Undem, le code est le suivant:
Importer java.util.concurrent.locks.lockSupport; / ** * créé par Cong le 2018/6/6. * / classe publique LocksupportTest {public static void main (String [] args) lance InterruptedException {thread thread = new Thread (new Runnable () {@Override public void run () {System.out.println ("Child Thread Park Start!"); // Appelez la méthode du parc et pendez-vous surcark! }}); // Démarrez le thread thread de l'enfant.Start (); // Le fil principal dort 1s thread.Sleep (1000); System.out.println ("Main Thread Uncark Start!"); // Appelez unbout pour laisser le fil de discussion maintenir la licence, puis la méthode du parc renverra Locksupport.Unpark (thread); }}Les résultats de l'opération sont les suivants:
Le code ci-dessus crée d'abord un fil de thread enfant. Après le démarrage, le fil d'enfant appelle la méthode du parc. Étant donné que le thread enfant par défaut ne détient pas de licence, il s'accrochera.
Le fil principal dort pendant 1s. Le but est que le thread principal appelle la méthode Unrecark et permet au thread de l'enfant de sortir le démarrage du parc de l'enfant! et bloc.
Le thread principal exécute ensuite la méthode UNTARK, le paramètre étant le thread enfant, le but est de permettre au thread enfant de maintenir la licence, puis la méthode du parc appelé par le thread de l'enfant renvoie.
Lors du retour de la méthode du parc, il ne vous dira pas quelle raison il revient. Par conséquent, l'appelant doit vérifier à nouveau si la condition est satisfaite en fonction de la méthode du parc dans l'appel actuel. S'il n'est pas rencontré, il doit à nouveau appeler la méthode du parc.
Par exemple, l'état d'interruption d'un fil lors de son retour, il peut être déterminé s'il revient car il est interrompu en fonction de la comparaison de l'état d'interruption avant et après l'appel.
Pour illustrer que le thread après l'appel de la méthode du parc reviendra après son interruption, modifiez l'exemple de code ci-dessus et supprimez LocksUpport.Unpark (thread); puis ajouter thread.interrupt (); Le code est le suivant:
Importer java.util.concurrent.locks.lockSupport; / ** * créé par Cong le 2018/6/6. * / classe publique LocksUpportTest {public static void main (String [] args) lance InterruptedException {thread thread = new Thread (new Runnable () {@Override public void run () {System.out.println ("Sous-thread Park start!"); // Appeler la méthode du parc et suspendre vous-même. ! // Démarrer l'enfant thread thread.start (); // le fil principal dort 1s thread.Sleep (1000); System.out.println ("Main Thread Uncark Start!"); // interrompre le thread de l'enfant.interrupt (); }}Les résultats de l'opération sont les suivants:
Comme le code ci-dessus, le thread enfant ne se terminera qu'après l'interrompu le thread de l'enfant. Si le fil d'enfant n'est pas interrompu, même si vous appelez unbout (thread), le thread enfant ne se terminera pas.
3. VOID Parknanos (Long Nanos) Méthode: Similaire à Park, si le parc d'appel de fil a obtenu la licence associée à Locksupport, puis appeler Locksupport.Park () reviendra immédiatement. La différence est que si le thread appelant le thread n'est pas obtenu, il sera suspendu puis revient après le temps des nanos.
Park prend également en charge trois méthodes avec des paramètres bloqueurs. Lorsqu'un fil appelle un parc sans contenir de licence et est bloqué et suspendu, l'objet Blocker sera enregistré à l'intérieur du fil.
Utilisez des outils de diagnostic pour observer la raison pour laquelle le fil est bloqué. Les outils de diagnostic utilisent la méthode getblocker (thread) pour obtenir l'objet bloqueur. Par conséquent, JDK recommande d'utiliser la méthode du parc avec des paramètres de bloqueur et de régler le bloqueur à cela, donc lorsque le vidage de mémoire résume le problème, nous pouvons savoir quelle classe est bloquée.
Les exemples sont les suivants:
Importer java.util.concurrent.locks.lockSupport; / ** * créé par Cong le 2018/6/6. * / public class testPark {public void testPark () {lockSupport.park (); // (1)} public static void main (String [] args) {testPark testPark = new testPark (); TestPark.TestPark (); }}Les résultats de l'opération sont les suivants:
Vous pouvez voir qu'il fait du blocage, nous devons donc utiliser les outils du répertoire JDK / bin pour jeter un œil. Si vous ne le savez pas, il est recommandé de consulter d'abord les outils de surveillance JVM.
Lorsque vous utilisez JSTACK PID pour afficher la pile de thread après l'exécution, les résultats que vous pouvez voir sont les suivants:
Ensuite, nous modifions le code ci-dessus (1) comme suit:
Locksupport.park (this); // (1)
Exécutez-le à nouveau et utilisez JSTACK PID pour afficher les résultats comme suit:
On peut voir qu'après la méthode du parc avec le bloqueur, la pile de threads peut fournir plus d'informations sur le blocage des objets.
Ensuite, nous vérifierons le code source de la fonction du parc (bloqueur d'objets), le code source est le suivant:
Public Static Void Park (objet Bloquer) {// Obtenez le thread de filetage d'appel t = Thread.currentThread (); // définir la variable de bloqueur setBlocker (t, bloqueur); // accroche le fil deafe.park (false, 0l); // efface la variable de bloqueur après l'activation du thread, car la raison est généralement analysée lorsque le thread est bloqué SetBlocker (t, null);}Il y a une variable dans la classe de threads volatile objet Parkblocker. Il est utilisé pour stocker l'objet bloqueur passé par le parc, c'est-à-dire que la variable de bloqueur est stockée dans la variable membre du thread appelant la méthode du parc.
4. La fonction Void Parknanos (bloqueur d'objets, nanos longs) a un délai de temps mort supplémentaire par rapport au parc (bloqueur d'objets).
5. VOID Parkuntil (bloqueur d'objets, échéance longue) Le code source de Parkuntil est le suivant:
Parkuntil public void statique public (bloqueur d'objets, longue limite) {Thread t = Thread.currentThread (); SetBlocker (T, bloqueur); // Isabsolute = true, time = date limite; signifie que disape.park (vrai, date limite); SetBlocker (t, null); }Vous pouvez voir qu'il s'agit d'une échéance définie, l'unité de temps est des millisecondes, qui est convertie en valeur après des millisecondes de 1970 au point actuel. La différence entre cela et Parknanos (bloqueur d'objets, nanos longs) est que le second calcule le temps des nanos en attente à partir de l'heure actuelle, tandis que le premier spécifie un moment temporel.
Par exemple, nous devons attendre jusqu'à 20:34 le 2018.06.06, puis convertir ce point de temps en nombre total de millisecondes de 1970 à ce moment.
Regardons un autre exemple, le code est le suivant:
Importer java.util.queue; import java.util.concurrent.concurrentLinkedQueue; import java.util.concurrent.atomic.atomicboolean; import java.util.concurrent.locks.locksupport; / ** * créé par congo le 2018/6/6. * / classe publique Fifomutex {private final atomicboolean verrouillé = new atomicboolean (false); file d'attente finale privée <read> serveurs = new concurrentLinkEdQueue <read> (); public void lock () {booléen a été interrupted = false; Courant du thread = thread.currentThread (); serveurs.add (courant); // Seul le fil de la tête de l'équipe peut obtenir le verrou (1) while (servers.peek ()! = Current ||! Locked ............pancard (false, true)) {lockSupport.park (this); if (Thread.Interrupted ()) // (2) a été Interrupted = true; } serveurs.remove (); if (a été Interrupted) // (3) current.interrupt (); } public void unlock () {Locked.Set (false); Locksupport.unpark (serveurs.peek ()); }}Vous pouvez voir qu'il s'agit d'un verrou du premier à l'abri, c'est-à-dire que seul l'élément d'en-tête de file d'attente peut l'obtenir. Code (1) Si le thread actuel n'est pas l'en-tête de file d'attente ou si le verrouillage actuel a été acquis par d'autres threads, appelez la méthode du parc pour se suspendre.
Alors le code (2) porte un jugement. Si la méthode du parc revient car elle est interrompue, l'interruption est ignorée et l'indicateur d'interruption est réinitialisé, et seul un drapeau est effectué, puis déterminez si le thread actuel est l'élément de tête de la file d'attente ou si le verrou a été acquis par d'autres threads. Si c'est le cas, continuez à appeler la méthode du parc pour vous accrocher.
Ensuite, si la marque est vraie dans le code (3), le thread sera interrompu. Comment comprenez-vous cela? En fait, d'autres fils ont interrompu le fil. Bien que je ne sois pas intéressé par le signal d'interruption et que je l'ignore, cela ne signifie pas que d'autres fils ne sont pas intéressés par le drapeau, je dois donc le restaurer.
Résumer
Ce qui précède est l'intégralité du contenu de cet article. J'espère que le contenu de cet article a une certaine valeur de référence pour l'étude ou le travail de chacun. Si vous avez des questions, vous pouvez laisser un message pour communiquer. Merci pour votre soutien à wulin.com.