首页 > 代码库 > Redis分布式锁实现

Redis分布式锁实现

直接上代码:

  1 package cn.wywk.yac.comm.redis;
  2 
  3 import org.slf4j.Logger;
  4 import org.slf4j.LoggerFactory;
  5 
  6 import redis.clients.jedis.Jedis;
  7 
  8 /**
  9  * ClassName: redis分布式锁实现 <br/>
 10  * date: 2017年2月17日 上午10:23:24 <br/>
 11  * 
 12  * @author 13414wuxinyu
 13  * @since JDK 1.8 调用示例: JedisPool pool=getPool(); Jedis
 14  *        jedis=pool.getResource(); RedisLock jedisLock = new
 15  *        RedisLock(jedis,"aa"); try { if (jedisLock.acquire()) { // 启用锁
 16  *        //执行业务逻辑 //.... //释放锁 jedisLock.release(jedis.get("aa")); } else {
 17  *        System.out.println("该线程未获得锁"); } } catch (Exception e) { // 分布式锁异常
 18  *        e.printStackTrace(); } finally { if (jedis != null) { try { //返回给连接池
 19  *        jedis.close(); } catch (Exception e) { e.printStackTrace(); } } }
 20  */
 21 public class RedisLock {
 22 
 23     protected final Logger log = LoggerFactory.getLogger(getClass().getName());
 24 
 25     /** 加锁标志 */
 26     private boolean locked = true;
 27 
 28     public String lockKey;
 29 
 30     private Jedis jedis;
 31 
 32     /** 获取锁默认超时时间(毫秒) */
 33     private int timeoutMsecs = 500;
 34 
 35     /** 锁的超时时间(毫秒),过期删除,默认10S */
 36     private int expireMsecs = 10000;
 37 
 38     public RedisLock(Jedis jedis, String lockKey) {
 39         this.jedis = jedis;
 40         this.lockKey = lockKey;
 41     }
 42 
 43     public RedisLock(Jedis jedis, String lockKey, int timeoutMsecs) {
 44         this(jedis, lockKey);
 45         this.timeoutMsecs = timeoutMsecs;
 46     }
 47 
 48     public RedisLock(Jedis jedis, String lockKey, int timeoutMsecs, int expireMsecs) {
 49         this(jedis, lockKey, timeoutMsecs);
 50         this.expireMsecs = expireMsecs;
 51     }
 52 
 53     /**
 54      * 加锁
 55      */
 56     public boolean acquire() throws InterruptedException {
 57         int timeout = timeoutMsecs;
 58         synchronized (this) {
 59             while (timeout >= 0) {
 60                 long expires = System.currentTimeMillis() + expireMsecs + 1;
 61                 String expiresStr = String.valueOf(expires); // 锁到期时间
 62                 if (jedis.setnx(lockKey, expiresStr) == 1) {
 63                     locked = true;
 64                     return true;
 65                 }
 66                 long SystemLong = System.currentTimeMillis();
 67                 String currentValueStr = jedis.get(lockKey); // redis里的时间
 68                 if (currentValueStr != null) {
 69                     long currentLong = Long.parseLong(currentValueStr);
 70                     long timeoutlong = SystemLong - currentLong;
 71                     if (timeoutlong > expireMsecs) {// 原线程持有锁超时
 72                         jedis.getSet(lockKey, expiresStr);
 73                         locked = true;
 74                         return true;
 75                     }
 76                 }
 77                 timeout -= 100;
 78                 Thread.sleep(100);
 79             }
 80         }
 81         return false;
 82     }
 83 
 84     /**
 85      * 解锁
 86      */
 87     public void release(String value) {
 88         try {
 89             release(jedis, value);
 90         } catch (Exception e) {
 91             e.printStackTrace();
 92             log.error(e.getMessage());
 93         }
 94     }
 95 
 96     private void release(Jedis jedis, String value) throws Exception {
 97         if (locked) {
 98             if (value.equals(jedis.get(lockKey))) {// 防止误释放其他线程的锁
 99                 jedis.del(lockKey);
100                 locked = false;
101             } else {
102                 throw new RuntimeException("Redis分布式锁释放失败,锁已被其他线程获得");
103             }
104         }
105     }
106 
107 }

测试代码:

 1 package cn.wywk.yac.redistest;
 2 import cn.wywk.yac.comm.redis.RedisLock;
 3 import redis.clients.jedis.Jedis;
 4 import redis.clients.jedis.JedisPool;
 5 import redis.clients.jedis.JedisPoolConfig;
 6 
 7 public class RedisLockTest implements Runnable{
 8     
 9      private static JedisPool pool = null;  
10      
11      private String xianName="";
12          
13      public RedisLockTest(){}
14      
15      public RedisLockTest(String name){
16          xianName=name;
17      }
18      
19     /** 
20      * 构建redis连接池 
21      *  
22      * @param ip 
23      * @param port 
24      * @return JedisPool 
25      */  
26     public static JedisPool getPool() {  
27         if (pool == null) {  
28             JedisPoolConfig config = new JedisPoolConfig();  
29             //控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;  
30             //如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。  
31             config.setMaxIdle(500);  
32             //控制一个pool最多有多少个状态为idle(空闲的)的jedis实例。  
33             config.setMaxIdle(5);  
34             //表示当borrow(引入)一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛出JedisConnectionException;  
35             config.setMaxWaitMillis(1000 * 100);  
36             //在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;  
37             config.setTestOnBorrow(true);  
38             pool = new JedisPool(config, "127.0.0.1",6379);  
39         }  
40         return pool;  
41     }  
42     
43     @Override
44     public void run() {
45         JedisPool pool=getPool();  
46         Jedis jedis=pool.getResource();
47         RedisLock jedisLock = new RedisLock(jedis,"aa");  
48         try {  
49             if (jedisLock.acquire()) { // 启用锁  
50                 System.out.println("线程名:"+xianName+"获得锁");
51                 //执行业务逻辑
52                 //....
53                 //释放锁
54                 System.out.println("线程名:"+xianName+"开始释放锁");
55                 jedisLock.release(jedis.get("aa"));
56             } else {  
57                 System.out.println("ERROR-线程名:"+xianName+"获取锁失败");
58             }  
59         } catch (Exception e) {  
60             // 分布式锁异常  
61             e.printStackTrace();
62         } finally {  
63             if (jedis != null) {  
64                 try {
65                     //返回给连接池
66                     jedis.close();
67                 } catch (Exception e) {
68                     e.printStackTrace();
69                 }  
70             }  
71         }
72     }
73         
74     public static void main(String[] args) throws InterruptedException {
75         for (int i = 0; i <40; i++) {
76             RedisLockTest test=new RedisLockTest("A线程"+i);
77             new Thread(test).start();
78             RedisLockTest test2=new RedisLockTest("B线程"+i);
79             new Thread(test2).start();
80         }
81     }
82 }

实现原理:

SETNX key value
功能:
当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。

GETSET命令
语法:
GETSET key value
功能:
将给定 key 的值设为 value ,并返回 key 的旧值 (old value),当 key 存在但不是字符串类型时,返回一个错误,当key不存在时,返回nil。

我们将value存一个时间戳,如果setNx失败,判断该key是否超时,如果未超时,在获得锁的指定时间内等待锁

如果该锁超时,说明上客户端持有锁的时间超时了,或者前一个客户端被意外中断死锁。(此处默认持有锁最长时间是10S),使用getset命令更新锁

测试我们启动了80个线程进行测试,未发现问题!若有问题,请大家指正,谢谢

Redis分布式锁实现