Le sémaphore est une classe couramment utilisée dans le package de juc. Il s'agit d'une application du mode de partage AQS. Il peut permettre à plusieurs threads de fonctionner sur des ressources partagées en même temps et peut contrôler efficacement le nombre de concurrence. Il peut atteindre un bon contrôle de la circulation. Semaphore fournit un concept de licence, qui peut être considéré comme un billet de bus. Seuls ceux qui obtiennent avec succès le billet peuvent monter dans le bus. Il y a un certain nombre de billets et il est impossible de les émettre sans restrictions, ce qui entraînera une surcharge du bus. Ainsi, lorsque le billet est émis (le bus est plein), les autres ne peuvent attendre que le prochain train. Si quelqu'un quitte le bus à mi-chemin, sa position sera gratuite, donc si les autres veulent monter dans le bus à ce moment, il peut retrouver des billets. Divers pools peuvent être mis en œuvre à l'aide du sémaphore. À la fin de cet article, nous rédigerons un pool de connexions de base de données simple. Tout d'abord, jetons un coup d'œil au constructeur du sémaphore.
// Constructeur 1Public Semaphore (int permets) {sync = new nonfairSync (permis);} // Constructeur 2Public Semaphore (int permis, booléen Fair) {sync = fair? Nouveau FairSync (permis): Nouveau non-FairSync (permis);}Semaphore fournit deux constructeurs sans paramètres, mais aucun constructeur sans paramètre n'est fourni. Les deux constructeurs doivent passer dans un nombre initial de licences. Le sémaphore construit à l'aide du constructeur 1 sera obtenu de manière non-FAIR lors de l'obtention de la licence. L'utilisation du constructeur 2 peut spécifier la méthode d'obtention de la licence via des paramètres (juste ou injuste). Le sémaphore fournit principalement deux types d'API au monde extérieur, obtenant des licences et licences de libération. La valeur par défaut consiste à obtenir et à libérer une licence, et les paramètres peuvent également être transmis pour obtenir et libérer plusieurs licences en même temps. Dans cet article, nous ne parlerons que de la situation d'obtention et de libération d'une licence à chaque fois.
1. Obtenir une licence
// Obtenir une licence (interruption de réponse) public void acquire () lance InterruptedException {sync.ACQUIRESHAREDIGNIBLBILLIBLLEMENT (1);} // obtient une licence (ne répondant pas à l'interruption) public void AcquireUnterrupblement () {sync.Acquireeshared (1);} // Tenter pour obtenir une licence (non-infirme) bootile publique () sync.NONFAIRSTRYACQUESHARED (1)> = 0;} // Essayez d'obtenir une licence (temps d'attente longue, unité TimeUnit) lance InterruptedException {return sync.tryacquireresharednanos (1, unit.tonanos (timeout));}L'API ci-dessus est l'opération d'acquisition de licence par défaut fournie par Semaphore. L'obtention d'une seule licence à la fois est également une situation courante dans la vie réelle. En plus de la récupération directe, il offre également une tentative de récupération. L'opération de récupération directe peut bloquer le fil après l'échec, tandis que la tentative de récupération non. Il convient également de noter que la méthode Tryacquire est utilisée pour essayer de l'obtenir de manière injuste. Ce que nous utilisons souvent à des temps normaux, c'est d'obtenir une licence. Voyons comment il est obtenu. Vous pouvez voir que la méthode Acquire appelle directement SYNC.ACQUIRESHAREDAGNEMBLEMENT (1). Cette méthode est la méthode dans AQS. Nous avons parlé une fois des articles AQS Source Code Series. Revoyons à nouveau.
// Acquérir le verrou en mode interrupable (mode partagé) public final void acquirearedaredinterrupblement (int arg) lance InterruptedException {// Déterminez d'abord si le thread est interrompu, si c'est le cas, lancez une exception si (thread.interrupted ()) {throw new InterruptedException (); } // 1. Essayez d'acquérir le verrou si (tryacQuireShared (arg) <0) {// 2. Si l'acquisition échoue, entrez la méthode DOACQUESHAREDARETRUBLIBLE (ARG); }}La première méthode acquisereadared, consiste à appeler la méthode TryAcquireshared pour essayer d'obtenir. TryAcquireShared est une méthode abstraite dans AQS. Les deux classes dérivées FairSync et non FairSync implémentent la logique de cette méthode. FairSync met en œuvre la logique de l'acquisition équitable, tandis que non-FairSync met en œuvre la logique de l'acquisition non-fair.
Résumé STATIC CLASS SYNC étend AbstractQueEuedSynchronizer {// Essayez d'obtenir Final int nonfairtryacquireShared (int acquére) {for (;;) {// obtenir des licences disponibles int disponibles = getState (); // obtient les licences restantes int restant = disponible - acquérir; // 1. Si vous restez moins de 0, retournez directement // 2. Si vous restez supérieur à 0, mettez à jour l'état de synchronisation d'abord, puis renvoyez restant if (restant <0 || comparabledSetState (disponible, restant)) {return restant; }}}} // NONFAIRSYNC STATIC FINAL CLASSE NONFAIRSYNC étend Sync {private static final long SerialVersionUID = -2694183684443567898l; NonfairSync (int permets) {super (permts); } // Essayez d'obtenir une licence protégée dans int tryacquireRared (int acquier) {return nonfairtryacquireShared (acquérir); }} // Fair Synchronizer la classe finale statique FairSync étend Sync {private static final long SerialVersionUID = 2014338818796000944l; FairSync (int permets) {super (permis); } // Essayez d'obtenir la licence protégée dans int tryacquireRared (int acquier) {pour (;;) {// juger s'il y a quelqu'un devant la file d'attente de synchronisation if (hasqueUedPredecessers ()) {// s'il y en a, retour -1, indiquant que la tentative d'obtenir un retour défaillant -1; } // Obtenez des licences disponibles dans disponibles = getState (); // obtient les licences restantes int restant = disponible - acquérir; // 1. Si vous restez moins de 0, revenez directement à // 2. Si le reste est supérieur à 0, l'état de synchronisation sera d'abord mis à jour puis renvoyé à la respect si (restant <0 || comparabledSetState (disponible, restant)) {return restant; }}}}Il convient de noter ici que la méthode TryAcQuirsared de non-FairSync appelle directement la méthode non fairtryacquirereshared, qui se trouve dans la synchronisation de la classe parent. La logique du verrouillage d'acquisition non-FAIR est de supprimer d'abord l'état de synchronisation actuel (l'état synchrone représente le nombre de licences), soustrayant le paramètre de l'état de synchronisation actuel. Si le résultat n'est pas inférieur à 0, il est prouvé qu'il existe toujours des licences disponibles, alors la valeur de l'état de synchronisation est directement mise à jour à l'aide de l'opération CAS, et enfin, la valeur de résultat sera retournée, que le résultat soit inférieur à 0. Ici, nous devons comprendre la signification de la valeur de retour de la méthode TryacquireShared. Le renvoi d'un nombre négatif signifie que l'échec de l'acquisition, zéro signifie que le thread actuel est acquis avec succès, mais le thread ultérieur ne peut plus être obtenu, et un nombre positif signifie que le thread actuel est acquis avec succès et le thread suivant peut également être obtenu. Regardons le code de la méthode acquiseredaredinterruptily.
// Acquérir des verrous en mode interruptible (mode partagé) public final void acquirearedaredinterrupblement (int arg) lève InterruptedException {// Déterminez d'abord si le thread est interrompu, si c'est le cas, lancez une exception si (thread.interrupted ()) {throw new InterruptedException (); } // 1. Essayez d'acquérir le verrouillage // Nombre négatif: indique que l'acquisition a échoué // Valeur zéro: indique que le thread actuel est acquis avec succès, mais le thread ultérieur ne peut plus obtenir // Numéro positif: indique que le thread actuel est acquis avec succès, et le thread suivant peut également réussir si (tryCquireared (Arg) <0) {// 2. Si l'acquisition échoue, entrez la méthode DOACQUESHAREDARETRUBLIBLE (ARG); }}Si le respect restant est inférieur à 0, cela signifie que l'acquisition a échoué. Par conséquent, TryAcQuirshared (arg) <0 est vrai, donc la méthode Doacquireshared Interruptily sera appelée Next. Lorsque nous avons parlé d'AQS, il enveloppera le fil actuel dans un nœud et le mettra dans la queue de la file d'attente de synchronisation, et il est possible de suspendre le fil. C'est aussi la raison pour laquelle les threads feront la queue et le bloc seront restants moins de 0. Si le respect restant> = 0 signifie que le thread actuel a été acquis avec succès. Par conséquent, TryAcQuireRared (Arg) <0 est une FLASE, de sorte que la méthode DOACQUIRAREDARETRUBLIBLE ne sera plus appelée pour bloquer le thread actuel. Ce qui précède est la logique entière de l'acquisition déloyale. Lors de l'acquisition d'une équitable, il vous suffit d'appeler la méthode HasqueUedPrecesses avant cela pour déterminer si quelqu'un fait la queue dans la file d'attente de synchronisation. Dans l'affirmative, le retour -1 indique directement que l'acquisition a échoué, sinon les étapes suivantes se poursuivent comme acquisition injuste.
2. Libérez la licence
// libérer une licence publique void release () {sync.reaseshared (1);}L'appel de la méthode de version consiste à libérer une licence. Son fonctionnement est très simple, nous appelons donc la méthode de relance des AQ. Jetons un coup d'œil à cette méthode.
// Opération de verrouillage de libération (mode partagé) public final booléen releaseshared (int arg) {// 1. Essayez de libérer le verrou si (tryreleSeshared (arg)) {// 2. Si la version réussit, réveillez-vous d'autres fils DoreleSeshared (); Retour Vrai; } return false;}La méthode releaseshared d'AQS appelle d'abord la méthode TryreleSeshared pour essayer de libérer le verrou. La logique d'implémentation de cette méthode est dans la synchronisation des sous-classes.
Résumé STATIC CLASS SYNC étend AbstractQueueEdSynchronizer {... // Essayez de libérer l'opération Boolean Final Boolean protégé TryReleSeshared (INT releases) {for (;;) {// Obtenez l'état de synchronisation actuel int Current = GetState (); // plus l'état de synchronisation actuel comme suit int next = Current + releases; // Si le résultat d'addition est inférieur à l'état de synchronisation actuel, une erreur sera signalée si (suivant <courant) {lancez une nouvelle erreur ("le nombre maximal de permis dépassé"); } // Mettez à jour la valeur de l'état de synchronisation en mode CAS, et renvoyez True si la mise à jour est réussie, sinon continuez à faire bouclez if (comparabledSetState (courant, suivant)) {return true; }}} ...}Vous pouvez voir que la méthode TryreleSeshared utilise une boucle pour tourner. Tout d'abord, obtenez l'état de synchronisation, ajoutez les paramètres entrants, puis mettez à jour l'état de synchronisation dans CAS. Si la mise à jour réussit, revenez vrai et sautez de la méthode. Sinon, la boucle se poursuivra jusqu'à ce qu'elle réussisse. Il s'agit du processus de libération de Semaphore la licence.
3. Écrivez manuellement un pool de connexions
Le code de sémaphore n'est pas très compliqué. L'opération couramment utilisée consiste à obtenir et à libérer une licence. La logique d'implémentation de ces opérations est relativement simple, mais cela n'encourage pas l'application répandue du sémaphore. Ensuite, nous utiliserons le sémaphore pour implémenter un pool de connexions de base de données simple. Grâce à cet exemple, nous espérons que les lecteurs pourront avoir une compréhension plus approfondie de l'utilisation de Semaphore.
classe publique ConnectPool {// Connexion Size de pool Private int Size; // Collection de connexion de la base de données Private Connect [] Connects; // Flag de connexion Flag privé booléen [] ConnectFlag; // Nombre restant de connexions disponibles INT privé volatile disponible; // sémaphore sémaphore sémaphore sémaphore; // Constructeur public connectPool (int size) {this.size = size; this.vailable = taille; Semaphore = nouveau sémaphore (taille, vrai); connects = new Connect [size]; ConnectFlag = new Boolean [Size]; initConnects (); } // Initialisez la connexion private void initConnects () {// générer un nombre spécifié de connexions de base de données pour (int i = 0; i <this.size; i ++) {connects [i] = new Connect (); }} // Obtenez la connexion de base de données private synchronisée connect getConnect () {for (int i = 0; i <connectFlag.length; i ++) {// transférer la collection pour trouver des connexions inutilisées if (! ConnectFlag [i]) {// Définissez la connexion dans In use ConnectFlag [i] = true; // Soustrayez le nombre de connexions disponibles disponibles--; System.out.println ("【" + thread.currentThread (). GetName () + "】 pour obtenir le nombre de connexions restantes:" + disponible); // renvoie la référence de connexion RETOUR Connects [i]; }} return null; } // Obtenez une connexion publique connect openConnect () lève InterruptedException {// obtenir licence semaphore.acquire (); // Obtenez la connexion de la base de données return getConnect (); } // Libérez une connexion publique synchronisée void release (connect connect) {for (int i = 0; i <this.size; i ++) {if (connect == connects [i]) {// définir la connexion sur connectFlag non utilisée [i] = false; // Ajouter 1 numéro de connexion disponible; System.out.println ("【" + thread.currentThread (). GetName () + "] pour libérer le numéro de connexion restant:" + disponible); // libérer la licence Semaphore.release (); }}} // Ajouter un nombre de connexions disponibles public int disponible () {return disponible; }}Code de test:
classe publique TestThread étend Thread {private static connectPool pool = new ConnectPool (3); @Override public void run () {try {connect connect = pool.openconnect (); Thread.Sleep (100); // prenez une pause pool.release (connect); } catch (InterruptedException e) {e.printStackTrace (); }} public static void main (String [] args) {for (int i = 0; i <10; i ++) {new TestThread (). start (); }}}Résultats des tests:
Nous utilisons un tableau pour stocker des références aux connexions de la base de données. Lors de l'initialisation du pool de connexions, nous appellerons la méthode initConnects pour créer un nombre spécifié de connexions de base de données et stocker leurs références dans le tableau. De plus, il existe un tableau de la même taille pour enregistrer si la connexion est disponible. Chaque fois qu'un thread externe demande pour obtenir une connexion, appelez d'abord la méthode Semaphore.Acquire () pour obtenir une licence, puis définissez l'état de connexion à utiliser et renvoyez enfin la référence à la connexion. Le nombre de licences est déterminé par les paramètres transmis pendant la construction. Le nombre de licences est réduit de 1 chaque fois que la méthode Semaphore.acquire () est appelée. Lorsque le nombre est réduit à 0, cela signifie qu'il n'y a pas de connexion disponible. Pour le moment, si d'autres threads l'obtiennent à nouveau, il sera bloqué. Chaque fois qu'un thread libère une connexion, Semaphore.release () sera appelé pour libérer la licence. Pour le moment, le nombre total de licences augmentera à nouveau, ce qui signifie que le nombre de connexions disponibles a augmenté. Ensuite, le fil précédemment bloqué se réveillera et continuera à obtenir la connexion. Pour le moment, vous pouvez obtenir la connexion avec succès en l'obtiant à nouveau. Dans l'exemple de test, un pool de connexions de 3 connexions est initialisé. Nous pouvons voir à partir des résultats du test que chaque fois qu'un thread obtient une connexion, le nombre de connexions restant sera réduite de 1. Lorsque le thread se réduit à 0, d'autres threads ne peuvent plus l'obtenir. À l'heure actuelle, vous devez attendre qu'un thread divulgue la connexion avant de continuer à l'obtenir. Vous pouvez voir que le nombre de connexions restantes change toujours entre 0 et 3, ce qui signifie que notre test a réussi.
Ce qui précède est tout le contenu de cet article. J'espère que cela sera utile à l'apprentissage de tous et j'espère que tout le monde soutiendra davantage Wulin.com.