java结合redis实现分布式锁

今天工作之余,查看一下利用redis来实现分布式锁,因此,在查看别人文章之余,自己也来手动模拟实现Java的lock接口,来自己手动实现一个分布式锁。拥有简单的加锁,解锁,锁中断等操作。

利用redis的分布式锁,主要还是利用redis的setnx命令,查看redis文档,可知次命令在redis缓存中添加数据的时候,如果key存在,则添加数据操作不成功。若不存在,才可以添加成功。从另外一个方面来理解锁(Lock),其实就是一种资源,在某个时刻标记为只能被某个线程使用,若资源已经被使用,则其他线程必须等待当前线程释放资源之后才可以使用。从这个方面理解,也就是,当前线程持有在redis缓存中key的资源,所以其他线程必须等待当前线程释放key的资源,否则只能等待。下面来贴上代码具体分析下:

[java]  view plain  copy
  1. package com.redislock;  
  2.   
  3. import redis.clients.jedis.Jedis;  
  4.   
  5. import java.util.concurrent.TimeUnit;  
  6. import java.util.concurrent.locks.Condition;  
  7. import java.util.concurrent.locks.Lock;  
  8.   
  9. /** 
  10.  * 尝试使用redis实现分布式锁 
  11.  * Created by hadoop on 2017/3/16. 
  12.  */  
  13. public class RedisLock implements Lock{  
[java]  view plain  copy
  1. /**jedis客户端**/  
  2. private final Jedis jedis;  
[java]  view plain  copy
  1. /**锁定资源的key**/  
  2. private final String lockName;  
  3. /**持有锁的最长时间**/  
  4. private final int expireTime = 300;  
  5. /**获取不到锁的休眠时间**/  
  6. private final long sleepTime = 100;  
  7. /**锁中断状态**/  
  8. private boolean interruped = true;  
  9. /**超时时间**/  
  10. private long expireTimeOut = 0;  
  11.   
  12. public RedisLock(Jedis jedis, String lockName){  
  13.     this.jedis = jedis;  
  14.     this.lockName = lockName;  
  15. }  
  16.   
  17. public void lock() {  
  18.     if (jedis == null)  
  19.         throw new NullPointerException("jedis is null");  
  20.     if (lockName == null)  
  21.         throw new NullPointerException("key is null");  
  22.     while (true){  
  23.         if (!interruped)  
  24.             throw new RuntimeException("获取锁状态被中断");  
  25.         long id = jedis.setnx(lockName, lockName);  
  26.         if (id == 0L){  
  27.             try {  
  28.                 Thread.currentThread().sleep(this.sleepTime);  
  29.             }catch (InterruptedException e){  
  30.                 e.printStackTrace();  
  31.             }  
  32.         }else{  
  33.             expireTimeOut = System.currentTimeMillis()/1000 + expireTime;  
  34.             jedis.expireAt(this.lockName, expireTimeOut);  
  35.             break;  
  36.         }  
  37.     }  
  38. }  
  39.   
  40. public void lockInterruptibly() throws InterruptedException {  
  41.     this.interruped = false;  
  42. }  
  43.   
  44. public boolean tryLock() {  
  45.     if (jedis == null)  
  46.         throw new NullPointerException("jedis is null");  
  47.     if (lockName == null)  
  48.         throw new NullPointerException("lockName is null");  
  49.     if (!interruped)  
  50.         throw  new RuntimeException("线程被中断");  
  51.     long id = jedis.setnx(lockName, lockName);  
  52.     if (id == 0L)  
  53.         return false;  
  54.     else {  
  55.         // 设置锁过期时间  
  56.         expireTimeOut = System.currentTimeMillis()/1000 + expireTime;  
  57.         jedis.expireAt(this.lockName, expireTimeOut);  
  58.         return true;  
  59.     }  
  60. }  
  61.   
  62. public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {  
  63.     if (jedis == null)  
  64.         throw new NullPointerException("jedis is null");  
  65.     if (lockName == null)  
  66.         throw new NullPointerException("lockName is null");  
  67.     if (time == 0)  
  68.         return false;  
  69.     long now = System.currentTimeMillis();  
  70.     long timeOutAt = now + calcSeconds(time, unit);  
  71.     while (true){  
  72.         if (!interruped)  
  73.             throw new InterruptedException("线程被中断");  
  74.         long id = jedis.setnx(this.lockName, this.lockName);  
  75.         // id = 0 表示加锁失败  
  76.         if(id == 0){  
  77.             // 获取锁超时  
  78.             if(System.currentTimeMillis() > timeOutAt)  
  79.                 return false;  
  80.             try {  
  81.                 // 休眠一段时间,继续获取锁  
  82.                 Thread.currentThread().sleep(this.sleepTime);  
  83.             }catch (InterruptedException e){  
  84.                 e.printStackTrace();  
  85.             }  
  86.         }else {  
  87.             //获取锁成功,设置锁过期时间  
  88.             expireTimeOut = System.currentTimeMillis()/1000 + expireTime;  
  89.             jedis.expireAt(this.lockName, expireTimeOut);  
  90.             return true;  
  91.         }  
  92.     }  
  93. }  
  94.   
  95. public void unlock() {  
  96.     try {  
  97.         //当前时间小于过期时间,则锁未超时,删除锁定  
  98.         if (System.currentTimeMillis() / 1000 < expireTimeOut)  
  99.             jedis.del(lockName);  
  100.     }catch (Exception e){  
  101.   
  102.     }finally {  
  103.         jedis.close();  
  104.     }  
  105. }  
  106.   
  107. public Condition newCondition() {  
  108.     throw new UnsupportedOperationException("不支持当前的操作");  
  109. }  
  110.   
  111. /** 
  112.  * 时间转换成毫秒 
  113.  * @param time 
  114.  * @param unit 
  115.  * @return 
  116.  */  
  117. private long calcSeconds (long time, TimeUnit unit){  
  118.     if (unit == TimeUnit.DAYS)  
  119.         return time * 24 * 60 * 60 * 1000;  
  120.     else if (unit == TimeUnit.HOURS)  
  121.         return time * 60 * 60 * 1000;  
  122.     else  if (unit == TimeUnit.MINUTES)  
  123.         return time * 60 * 1000;  
  124.     else  
  125.         return time * 1000;  
  126. }  
在这里,第一,我是利用实现Lock接口的方式来实现这个锁,在使用锁的时候,一般为一个线程持有一把锁,因此锁可以看成是在线程自己的内部堆栈里面,因此可以不用考虑多线程的支持。重点是在获取锁的接口,获取锁意味着对资源的竞争,因此在本示例中,我尝试循环获取对象的锁,也就是循环向redis里面添加数据,添加成功,则代表加锁成功,添加不成功,则代表加锁失败,休眠一段时间之后,继续获取锁。因此这个lock方法为阻塞的,获取不到锁,就会一直去获取。newCondition方法没有实现,也是基于目前才疏学浅,还不回使用,后面会考虑加上这个newCondition方法。

在这里,说两点,第一点就是redis key的超时,这样可以保证在客户端程序突然down的时候,资源可以在一段时间之后被释放掉,不会产生死锁。还有就是unlock()这个方法,在加锁的时候,会计算锁释放的时间,在释放锁的时候,要判定当前锁是否超时了,若超时,怎不能删除key。否则会破坏线程的安全性。因为不用的redis客户端,虽然不能setnx key值,但是可以del这个key。当然,我这里是一种最简单的处理,在临界条件下也容易出现问题,只是目前可以想到的一种办法。也希望大神们不吝赐教小弟我。

第二点需要说明的是中断状态,基于以前看多线程安全结束的模式,在此处我也是设置标志量interruped 用来标识当前的锁已经被中断,在进行任何操作前,都会先判定这个中断标志,若被中断,则抛出程序异常。 目前还不知道这样做到底会如何,希望路过大神给指导一下意见!!!完全自学,希望不吝赐教。在写的过程中,考虑到一件事情,若中断标志被改变,也就是锁被中断,是否需要调用unlock()方法释放这个锁???

猜你喜欢

转载自blog.csdn.net/ly199108171231/article/details/79027612