Detailed Java how to implement the distributed lock _java based on Redis

Source: Internet
Author: User
Tags redis static class visibility volatile

Objective

Single JVM synchronization is good, directly with the JDK provided by the lock can be, but it is certainly impossible to synchronize across processes, in this case must rely on a third party, I have implemented here with Redis, of course, there are many other ways to achieve. In fact, based on the principle of REDIS implementation is relatively simple, before looking at the code suggested that everyone first to see the principle, read the code should be easy to understand after reading.

I do not implement the JDK java.util.concurrent.locks.Lock interface here, but customize one, because the JDK has a newCondition method that I have not implemented here for a while. This lock provides a variant of the 5 lock methods, you can choose which one to use to get the lock, and my idea is to use the method of returning with a time-out, because otherwise, if Redis hangs, the thread will always die in that cycle (for here, it should be further optimized, If Redis hangs, Jedis's operation will definitely throw the exception and so on, you can define a mechanism for Redis to notify users of this lock, or thread, when they hang up.

Package cc.lixiaohui.lock;

Import Java.util.concurrent.TimeUnit;

Public interface Lock {

 /**
 * Blocking acquisition lock, does not respond to interrupt
 /void lock;
 
 /**
 * Blocking acquisition lock, response interrupt
 * 
 * @throws interruptedexception/
 void lockinterruptibly throws interruptedexception;
 
 /**
 * Attempt to acquire a lock, get no immediate return, do not block *
 * *
 boolean trylock;
 
 /**
 * Timeout automatically returns a blocking acquisition lock that does not respond to interrupts
 * 
 * @param time
 * @param unit
 * @return {@code true} if the lock is successfully acquired, {@code False} if the lock */Boolean trylock is not taken within a specified time
 (Long, timeunit);
 
 /**
 * Timeout automatically return blocking acquisition lock, response interrupt
 * * @param time
 * @param unit
 * @return {@code true} if the lock is successfully acquired, {@code F Alse} If the lock is not acquired within a specified time
 @throws interruptedexception The current thread attempting to acquire the lock is
 interrupted
 /boolean trylockinterruptibly ( Long time, Timeunit unit) throws interruptedexception;
 
 /**
 * Release lock
 /void Unlock
 
}

See its abstract implementation:

Package cc.lixiaohui.lock;

Import Java.util.concurrent.TimeUnit;
 /** * Lock skeleton implementation, the real acquisition of the lock steps by subclasses to implement. * * @author Lixiaohui * */Public abstract class Abstractlock implements Lock {/** * <pre> * There is no need to ensure that visibility is warranted
 On, because it is a distributed lock, * 1. It is also possible for multiple threads of the same JVM to use different lock objects, in which case there is no need to guarantee visibility * 2. The visibility must be guaranteed when multiple threads of the same JVM use the same lock object.

 * </pre> * * protected volatile Boolean locked;

 /** * The thread holding the lock in the current JVM (if have one)/private thread exclusiveownerthread;
 public void Lock {try {lock (false, 0, NULL, FALSE); catch (Interruptedexception e) {//TODO ignore}} public void lockinterruptibly throws Interruptedexception {lo
 CK (FALSE, 0, NULL, true);
 public Boolean Trylock (long, timeunit unit) {try {return lock (True, time, Unit, false);
 The catch (Interruptedexception e) {//TODO Ignore} return false; public Boolean trylockinterruptibly (long, Timeunit unit) throws Interruptedexception {return lock (True, Time, u
 NIT, true); } public void Unlock {//TODO Check whether the current thread holds the lock if (Thread.CurrentThread!= getexclusiveownerthread) {throw new Illegalmonitorstateexception ("Curren
 T thread does not hold the lock);
 } unlock0;
 Setexclusiveownerthread (NULL);
 } protected void Setexclusiveownerthread (thread thread) {exclusiveownerthread = thread;
 } protected final Thread Getexclusiveownerthread {return exclusiveownerthread;
 
 } protected abstract void unlock0; /** * Blocking Acquisition Lock implementation * * @param usetimeout * @param time * @param unit * @param Interrupt Response Interrupt * @return * @throws  Interruptedexception */Protected Abstract Boolean lock (Boolean usetimeout, long time, Timeunit unit, Boolean interrupt)

Throws Interruptedexception; }

Based on the final implementation of the Redis, the key acquisition lock, the code that releases the lock in the lock method and unlock0 method of this class, you can just look at both methods and write it all by yourself:

Package cc.lixiaohui.lock;

Import Java.util.concurrent.TimeUnit;

Import Redis.clients.jedis.Jedis; /** * <pre> * Distributed locks implemented based on Redis SETNX operations it is best to lock (long time, timeunit unit) to avoid network problems and cause threads to block * <a HR ef= "HTTP://REDIS.IO/COMMANDS/SETNX" &GT;SETNC Operations Reference </a> * </pre> * * @author Lixiaohui * */public class
 
 Redisbaseddistributedlock extends Abstractlock {private Jedis Jedis;
 
 The name of the lock protected String lockkey;
 
 The effective time of the lock (milliseconds) protected long lockexpires;
 Public Redisbaseddistributedlock (Jedis Jedis, String Lockkey, long lockexpires) {This.jedis = Jedis;
 This.lockkey = Lockkey;
 This.lockexpires = Lockexpires; The implementation of the//blocking acquisition lock protected Boolean lock (Boolean usetimeout, long time, Timeunit unit, Boolean interrupt) throws interrupt
 edexception{if (interrupt) {checkinterruption;
 Long start = System.currenttimemillis; Long timeout = Unit.tomillis (time); If!usetimeout, then it ' s useless while usetimeout? Istimeout (Start, Timeout): true) {if (interrupt) {checkinterruption; Long lockexpiretime = System.currenttimemillis + lockexpires + 1;//lock timeout time String stringoflockexpiretime = String.valu
 
 EOf (Lockexpiretime);
 if (Jedis.setnx (Lockkey, stringoflockexpiretime) = = 1) {//Get to lock//TODO successfully acquire to lock, set relevant identity locked = TRUE;
 Setexclusiveownerthread (Thread.CurrentThread);
 return true;
 String value = Jedis.get (Lockkey); if (value!= null && istimeexpired (value)) {//Lock is expired//assume multiple threads (not single JVM) come here at the same time String OldValue = JEDIS.G Etset (Lockkey, stringoflockexpiretime); Getset is atomic//But the oldValue that each thread gets is certainly not the same (because the Getset is atomic)//join OldValue is still expired, then it means that the lock was obtained if (oldValue
  != null && istimeexpired (oldValue)) {//TODO successfully acquire lock, set relevant identity locked = TRUE;
  Setexclusiveownerthread (Thread.CurrentThread);
 return true;
 } else {//TODO lock isn't expired, enter Next loop retrying}} return false; Public boolean Trylock {long lockexpiretime = System. Currenttimemillis + lockexpires + 1;//lock timeout time String stringoflockexpiretime = string.valueof (lockexpiretime);
 if (Jedis.setnx (Lockkey, stringoflockexpiretime) = = 1) {//Get to lock//TODO successfully acquire to lock, set relevant identity locked = TRUE;
 Setexclusiveownerthread (Thread.CurrentThread);
 return true;
 String value = Jedis.get (Lockkey); if (value!= null && istimeexpired (value)) {//Lock is expired//assume multiple threads (not single JVM) come here at the same time String OldValue = JEDIS.G Etset (Lockkey, stringoflockexpiretime); Getset is atomic//But the oldValue that each thread gets is certainly not the same (because Getset is atomic)//If the oldValue is still expired, then it means that the lock was obtained if (oldValue
 != null && istimeexpired (oldValue)) {//TODO successfully acquire lock, set relevant identity locked = TRUE;
 Setexclusiveownerthread (Thread.CurrentThread);
 return true;
 } else {//TODO lock isn't expired, enter Next loop retrying} return false;
 }/** * Queries If this lock was held by any thread. * * @return {@code true} if any thread holds this lock and * {@code false} otherwise/pubLic Boolean islocked {if (locked) {return true;
 else {String value = Jedis.get (Lockkey); TODO, there's actually a problem here, think: When the Get method returns value, assuming that value is already out of date,//And in this moment, the other node sets the value, and the lock is held by another thread (the node holds), and the next decision//is not detected.
 However, this problem should not lead to other problems because the purpose of this method is//is not synchronous control, it is only a lock status report.
 return!istimeexpired (value);
 } @Override protected void Unlock0 {//TODO to determine whether the lock expires String value = Jedis.get (Lockkey);
 if (!istimeexpired (value)) {Dounlock; }} private void Checkinterruption throws Interruptedexception {if (Thread.currentThread.isInterrupted) {throw new I
 Nterruptedexception;
 } Private Boolean istimeexpired (String value) {return Long.parselong (value) < System.currenttimemillis;
 Private Boolean istimeout (long start, long timeout) {return start + timeout > system.currenttimemillis;
 private void Dounlock {Jedis.del (Lockkey); }

}

If there is another way to implement (for example zookeeper ), then directly inherit AbstractLock and implement L, the ock(boolean useTimeout, long time, TimeUnit unit, boolean interrupt) unlock0 method can be (so-called abstract)

Test

Simulate the global ID growth and design a IDGenerator class that is responsible for generating a global increment ID with the following code:

 package cc.lixiaohui.lock;
Import Java.math.BigInteger;

Import Java.util.concurrent.TimeUnit; /** * Analog ID generation * @author lixiaohui * * */public class Idgenerator {private static BigInteger id = biginteger.valueof (

 0);

 Private final lock lock;

 private static final BigInteger INCREMENT = biginteger.valueof (1);
 Public Idgenerator (lock lock) {This.lock = lock; Public String Getandincrement {if (Lock.trylock (3, timeunit.seconds)) {try {//TODO gets to lock here, Access critical section resource return get
 AndIncrement0;
 finally {Lock.unlock;
 } return null;
 return getAndIncrement0;
 private string GetAndIncrement0 {string s = id.tostring;
 id = id.add (INCREMENT);
 return s; }
}

Test main logic: Open two threads in the same JVM dead loop (no interval between loops, some words test meaningless) get ID (I'm not a dead loop here but run 20s), get the ID to save to the same Set inside, Check to see if the ID exists in set before saving, and then stop two threads if it already exists. If the program can run out of 20s, then the distributed lock can be counted to meet the requirements, so the effect of the test should be the same as in different JVMs (that is, in a real distributed environment), and the following is the code for the test class:

Package cc.lixiaohui.DistributedLock.DistributedLock;
Import Java.util.HashSet;

Import Java.util.Set;

Import Org.junit.Test;
Import Redis.clients.jedis.Jedis;
Import Cc.lixiaohui.lock.IDGenerator;
Import Cc.lixiaohui.lock.Lock;

Import Cc.lixiaohui.lock.RedisBasedDistributedLock;
 
 public class Idgeneratortest {private static set<string> Generatedids = new hashset<string>;
 private static final String Lock_key = "Lock.lock";
 
 Private static final Long Lock_expire = 5 * 1000;
 @Test public void Test throws Interruptedexception {Jedis jedis1 = new Jedis ("localhost", 6379);
 Lock lock1 = new Redisbaseddistributedlock (Jedis1, Lock_key, Lock_expire);
 Idgenerator G1 = new Idgenerator (LOCK1);
 
 Idconsumemission consume1 = new Idconsumemission (G1, "consume1");
 Jedis jedis2 = new Jedis ("localhost", 6379);
 Lock Lock2 = new Redisbaseddistributedlock (Jedis2, Lock_key, Lock_expire);
 Idgenerator g2 = new Idgenerator (LOCK2); Idconsumemission consume2 = new IdconsumemissIon (G2, "consume2");
 thread T1 = new Thread (CONSUME1);
 Thread t2 = new Thread (consume2);
 T1.start;
 
 T2.start; Thread.Sleep (20 * 1000);
 
 Let two threads run for 20 seconds idconsumemission.stop;
 T1.join;
 T2.join;
 Static String Time {return string.valueof (system.currenttimemillis/1000);
 
 Static Class Idconsumemission implements Runnable {private Idgenerator idgenerator;
 
 private String name;
 
 Private static volatile Boolean stop;
 Public Idconsumemission (Idgenerator idgenerator, String name) {this.idgenerator = Idgenerator;
 THIS.name = name;
 public static void Stop {stop = true;
 public void Run {System.out.println (time + ": Consume" + name + "Start");
 while (!stop) {String id = idgenerator.getandincrement;
  if (Generatedids.contains (ID)) {System.out.println (Time + ": Duplicate ID generated, id =" + ID);
  Stop = true;
 Continue
 } generatedids.add (ID);
 System.out.println (Time + ": Consume" + name + "Add ID =" + ID); } System.out.prinTLN (Time + ": Consume" + name + "Done"); }
 
 }
 
}

To illustrate, I'm not very good at stopping two threads here, I do it for convenience, because it's just a test, it's best not to do so.

Test results

Run 20s printing things too much, the front of the printed is clear , only almost run out of time, the following screenshot. Explains that the lock works:

When IDGererator there is no lock (that is, IDGererator the getAndIncrement method is not locked in the internal acquisition id ), the test is not passed, very large probability will stop midway, the following is not locked test results:

This is less than 1 seconds:

This is not even 1 seconds:

Conclusion

OK, the above is the Java implementation based on Redis distributed lock of the entire content, if you find the problem hope to correct, I hope this article can be for everyone's study and work to bring certain help, if there is doubt can message exchange.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.