Préface
La synchronisation dans un seul JVM est facile à gérer. Utilisez simplement le verrou fourni par JDK directement, mais la synchronisation transversale est certainement impossible. Dans ce cas, vous devez compter sur un tiers. J'utilise Redis ici, et bien sûr, il existe de nombreuses autres méthodes de mise en œuvre. En fait, le principe basé sur la mise en œuvre de Redis est assez simple. Avant de lire le code, il est recommandé de vérifier d'abord le principe. Après avoir lu le code, il devrait être plus facile à comprendre.
Je n'implémente pas l'interface java.util.concurrent.locks.Lock de JDK, mais en personnalisez une, car il existe une méthode newCondition dans JDK. Je ne l'ai pas implémenté pour le moment. Ce verrouillage fournit 5 variantes de méthodes de verrouillage. Vous pouvez choisir celui à utiliser pour obtenir le verrou. Mon idée est qu'il est préférable d'utiliser les méthodes avec le retour de temps mort. Parce que si ce n'est pas le cas, si Redis est suspendu, le fil sera toujours dans la boucle morte (à ce sujet, il devrait être encore optimisé. Si Redis est accroché, le fonctionnement de Jedis lancera certainement des exceptions, etc.
package cc.lixiaoHUi.lock; import java.util.concurrent.TimeUnit; verrouillage d'interface publique {/ ** * Bloquer le verrouillage de l'acquisition, ne répondant pas à l'interruption * / void Lock; / ** * Bloquer le verrouillage de l'acquisition, ne répondant pas à l'interruption * * @Throws InterruptedException * / void Lockinterrupablement lance InterruptedException; / ** * Essayez d'acquérir le verrou, retournez immédiatement sans l'obtenir, ne bloquant pas * / booléen trylock; / ** * Le verrouillage d'acquisition de blocage renvoyé automatiquement par le délai d'expiration, ne répondant pas à l'interruption * * @param time * @param unité * @return {@code true} Si le verrou est acquis avec succès, {@code false} Si le verrou est récupéré dans le temps spécifié * * / BOOLEAN TRYLOCK (longue durée); /** * The blocking acquisition lock automatically returned by timeout, response interrupt* * @param time * @param unit * @return {@code true} If the lock is successfully acquired, {@code false} If the lock is not retrieved within the specified time* @throws InterruptedException The current thread trying to acquire the lock is interrupted*/ boolean tryLockInterruptibly(long time, TimeUnit unit) throws InterruptedException; / ** * Lock de libération * / void unlock; }Regardez sa mise en œuvre abstraite:
Package CC.LixiaoHUi.lock; Importer Java.util.concurrent.TimeUnit; / ** * L'implémentation squelette du verrou, les étapes réelles pour acquérir le verrou sont implémentées par sous-classes. * * @author lixiaohui * */public abstract class AbstractLock implements Lock { /** * <pre> * Whether visibility needs to be guaranteed here is worth discussing, because it is a distributed lock, * 1. It is also possible for multiple threads of the same jvm to use different lock objects, and in this case there is no need to guarantee visibility* 2. Multiple threads of the same jvm to use the same lock object, then visibility must être garanti. * </ pre> * / Boolean volatile protégé verrouillé; / ** * Le thread tenant actuellement le verrou dans JVM (si en a un) * / Filor privé ExclusiveOwnerThread; public void Lock {try {lock (false, 0, null, false); } catch (InterruptedException e) {// todo ignore}} public void lockinterruply lance InterruptedException {lock (false, 0, null, true); } public boolean trylock (longue durée, unité timeunit) {try {return Lock (true, time, unit, false); } catch (InterruptedException e) {// todo ignore} return false; } public boolean trylockInterruply (longue durée, unité timeunit) lève InterruptedException {return Lock (true, time, unit, true); } public void unlock {// todo vérifiez si le thread actuel maintient le verrou si (thread.currentThread! = GetExclusiveOwnerThread) {Throw New illégalMonitorStateException ("Le thread actuel ne tient pas le verrou"); } unlock0; setExclusiveOwnerThread (null); } protégé void setExclusiveOwnerThread (thread thread) {exclusifOwnerThread = thread; } Final Protected Final GetExclusiveOwnerThread {return exclusifOwnerThread; } Résumé protégé void unlock0; / ** * Implémentation du blocage du verrouillage de l'acquisition * * @param useeTimeout * @param time * @param unité * @param interruption de répondre aux interruptions * @return * @throws interruptedException * / protégée abstraite Boolean Lock (boolean useetimeout, longue durée, unité de tempsunit, booléen interruption) lance InterruptedException;} Sur la base de l'implémentation finale de Redis, le code clé pour acquérir et libérer le verrou est dans la méthode lock et unlock0 la méthode de cette classe. Vous ne pouvez regarder ces deux méthodes et en écrire complètement vous-même:
Package cc.lixiaoHUi.lock; import java.util.concurrent.timeunit; import redis.clients.jedis.jedis; / ** * <pre> * verrouillage distribué implémenté par un fonctionnement setnx basé sur Redis * * Il est préférable d'utiliser le verrouillage (unité de temps de temps * * href = "http://redis.io/comands/setnx"> Setnc Operation Reference </a> * </ pre> * * @author lixiaoHUi * * / public class redisbasedDistributeLock étend Abstractlock {Jedis privé Jedis; // le nom du verrou de serrure protégée de verrouillage; // La durée de validité de la serrure (MS) a protégé LocKExpires; Public RedisbasedDistributedLock (Jedis Jedis, String Lockkey, Long Lockexpires) {this.Jedis = Jedis; this.lockKey = LockKey; this.locKExpires = lockexpires; } // Implémentation du blocage du verrouillage d'acquisition Lock booléen protégé (booléen useetimeout, longue durée, unité de tempsunit, booléen interruption) lance InterruptedException {if (interrupt) {CheckInterruption; } long start = System.currentTimeMillis; Timeout long = Unit.tomiillis (temps); // Si! UseeTimeout, alors c'est inutile while (useTimeOut? Istimeout (start, timeout): true) {if (interrupt) {CheckInterruption; } long lockExpiretime = System.Currenttimemillis + lockExpires + 1; // Lock Timeout String String StringoflocKExpiretime = String.ValueOf (lockExpiretime); if (Jedis.Setnx (Lockkey, stringoflocKExpiretime) == 1) {// obtenu Lock // TODO a réussi à obtenir le verrou, à définir l'identifiant pertinent Locked = true; setExclusifOwnerThread (Thread.currentThread); Retour Vrai; } String value = jedis.get (Lockkey); if (value! = null && isTimeExpired (value)) {// Lock est expiré // Supposons que plusieurs threads (non-single jvm) viennent ici en même temps String OldValue = jedis.getSet (Lockkey, stringoflocKExpiretime); // getset est atomic // mais la vieille valeur obtenue par chaque fil lorsqu'il s'agit ici est certainement impossible d'être la même (car GetSet est atomique) // L'ancienvalue obtenu en rejoignant est toujours expiré, alors cela signifie que la serrure est obtenue si (OldValue! = Null && isTimeExpired (OldValue) {// ToDo a réussi à obtenir le verrouillage, à être pertinent pour le pertinence). setExclusifOwnerThread (Thread.currentThread); Retour Vrai; }} else {// Todo Lock n'est pas expiré, entrez la boucle Next Retrying}} return false; } public boolean trylock {long lockExpiretime = system.currenttimemillis + lockexpires + 1; // verrouiller le temps de temps string stringoflocKExpiretime = string.valueof (lockExpiretime); if (Jedis.Setnx (Lockkey, stringoflocKexpiretime) == 1) {// Obtenez le verrouillage // TODO acquiert avec succès le verrou, définissez l'identifiant pertinent Locked = true; setExclusifOwnerThread (Thread.currentThread); Retour Vrai; } String value = jedis.get (Lockkey); if (value! = null && isTimeExpired (value)) {// Lock est expiré // Supposons plusieurs threads (pas un seul jvm), venez ici en même temps OldValue = jedis.getSet (Lockkey, stringoflocKExpiretime); // getSet est atomic // mais la vieille VALUe obtenue par chaque fil lorsqu'il s'agit ici est certainement impossible (car GetSet est atomique) // Si l'ancienvalue que vous obtenez est toujours expiré, alors cela signifie que vous avez le verrou si (OldValue! = Null && isTimeExpired (OldValue)) {// TODO a obtenu avec succès la serrure, Set the pertinent identified = true; setExclusifOwnerThread (Thread.currentThread); Retour Vrai; }} else {// Todo Lock n'est pas expiré, entrez la boucle suivante RETRYing} return false; } / ** * requêtes si ce verrou est maintenu par un thread. * * @return {@code true} si un thread conserve ce verrou et * {@code false} sinon * / public boolean islocked {if (Locked) {return true; } else {String value = jedis.get (Lockkey); // TODO Il y a en fait un problème ici. Pensez: Lorsque la méthode GET renvoie la valeur, supposons que la valeur a expiré, // à ce moment, un autre nœud définit la valeur et que la serrure est maintenue par un autre thread (le nœud tient), et le jugement suivant // ne peut pas détecter cette situation. Cependant, ce problème ne doit pas causer d'autres problèmes, car le but de cette méthode est // n'est pas un contrôle synchrone, c'est juste un rapport de l'état de verrouillage. return! isTimeExpired (valeur); }} @Override Protected void unlock0 {// TODO détermine si le verrouillage expire la valeur de chaîne = jedis.get (Lockkey); if (! isTimeExpired (value)) {Dounlock; }} private void CheckInterruption lance InterruptedException {if (thread.currentThread.isterrupted) {Throw new interruptedException; }} private boolean isTimeExpired (String Value) {return long.parselong (valeur) <System.currentTimemillis; } private booléen iStimeout (longueur long, timeout long) {return start + timeout> System.currenttimemillis; } private void Dounlock {Jedis.del (Lockkey); }} Si vous modifiez la méthode d'implémentation à l'avenir (comme zookeeper , etc.), vous pouvez directement hériter AbstractLock et implémenter L ock(boolean useTimeout, long time, TimeUnit unit, boolean interrupt) et unlock0 la méthode (soi-disant abstraction)
test
Simulez le producteur d'ID global et concevez une classe IDGenerator . Cette classe est responsable de la génération d'ID incrémentiels globales. Son code est le suivant:
Package cc.lixiaoHUi.lock; import java.math.bigInteger; import java.util.concurrent.timeUnit; / ** * Simulater ID Generation * @author lixiaoHUi * * / public class idGenerator {private static bigInteger id = bigInteger.valueof (0); Lock de verrouillage final privé; Incrément final statique statique privé = bigInteger.Valueof (1); public idGenerator (Lock Lock) {this.lock = Lock; } public String getAndIncrement {if (lock.trylock (3, timeunit.seconds)) {try {// too obtenez le verrou ici et accédez à la ressource de zone critique return getAndInCment0; } enfin {lock.unlock; }} return null; // return getAndIncrement0; } String privé getAndIncrement0 {String S = id.toString; id = id.add (incrément); retour s; }} Testez la logique principale: ouvrez deux threads dans le même JVM pour boucler mortel (il n'y a pas d'intervalle entre les boucles, s'il y en a un, le test sera dénué de sens) pour obtenir ID (je ne suis pas une boucle morte mais fonctionne pendant 20s), obtenez l'ID et stockez-le dans le même Set . Avant qu'il ne soit stocké, vérifiez si l' ID existe dans set . S'il existe déjà, laissez les deux fils s'arrêter. Si le programme peut fonctionner normalement 20 secondes, cela signifie que ce verrou distribué peut répondre aux exigences. L'effet d'un tel test devrait être le même que celui de différents JVM (c'est-à-dire dans un environnement réel distribué). Ce qui suit est le code de la classe de test:
Package C.LixiaoHUi.DistributedLock.DistributedLock; Import java.util.hashset; import java.util.set; import org.junit.test; import redis.clients.jedis.jeded; CC.LIXIAOHUI.LOCK.redisBasedDistributedLock; public class IdGenerAtOrest {private static set <string> génératedIdS = new HashSet <string>; String final statique privé Lock_Key = "Lock.lock"; Final statique privé Long Lock_Expire = 5 * 1000; @Test Public Void Test lance InterruptedException {Jedis Jedis1 = New Jedis ("LocalHost", 6379); LOCK LOCK1 = NOUVEAU REDISBASTIONDDISTRIBITEDLOCK (JEDIS1, LOCK_KEY, LOCK_EXPIRE); IdGenerator G1 = new IdGenerator (Lock1); Idconsumission Consume1 = new idconsumission (G1, "Consume1"); Jedis Jedis2 = New Jedis ("LocalHost", 6379); LOCK LOCK2 = NOUVEAU REDISBASTIONDDISTRIBITEDLOCK (JEDIS2, LOCK_KEY, LOCK_EXPIRE); IdGenerator G2 = new IdGenerator (Lock2); Idconsumission Consume2 = new IdConsumission (G2, "Consume2"); Thread t1 = nouveau thread (Consume1); Thread t2 = nouveau thread (Consume2); T1.Start; T2.Start; Thread.Sleep (20 * 1000); // Laissez deux threads fonctionner pendant 20 secondes idconsumission.stop; t1.join; t2.join; } Statique String Time {return String.ValueOf (System.CurrentTimeMillis / 1000); } classe statique idConsumission implémente Runnable {private idGenerator idGenerator; nom de chaîne privé; Arrêt booléen volatile statique privé; public idConsumission (idGenerator idGenerator, String Name) {this.idGenerator = idGenerator; this.name = name; } public static void stop {stop = true; } public void run {System.out.println (temps + ": consommer" + name + "start"); while (! stop) {String id = idGenerator.getAndInCment; if (generatedS.Contains (id)) {System.out.println (temps + ": id duplicate généré, id =" + id); stop = true; continuer; } généréd.add (id); System.out.println (Time + ": Consume" + Name + "Add id =" + id); } System.out.println (temps + ": consommer" + name + "Done"); }}}Pour être clair, la façon dont j'arrête deux fils ici n'est pas très bonne. Je l'ai fait pour plus de commodité, car ce n'est qu'un test, il est donc préférable de ne pas le faire.
Résultats des tests
Il y a trop de choses imprimées dans les années 20. Ceux imprimés à l'avant sont clear et disponibles uniquement lorsque la course est presque terminée. La capture d'écran ci-dessous. Cela montre que cette serrure fonctionne normalement:
Lorsque IDGererator n'est pas verrouillé (c'est-à-dire que la méthode getAndIncrement d' IDGererator ne le verrouille pas lorsqu'elle obtient id en interne), le test ne passe pas et il y a une très grande probabilité qu'elle s'arrête à mi-chemin. Voici les résultats des tests lorsque le verrou n'est pas verrouillé:
Cela prend moins de 1 seconde:
Celui-ci prend moins de 1 seconde:
Conclusion
OK, ce qui précède est tout au sujet de Java implémentant les verrous distribués basés sur Redis. Si vous trouvez des problèmes, vous espérez les corriger. J'espère que cet article peut vous aider à étudier et à travailler. Si vous avez des questions, vous pouvez laisser un message pour communiquer.