设为首页 收藏本站
查看: 664|回复: 0

[经验分享] Java实现基于Redis的分布式锁

[复制链接]

尚未签到

发表于 2018-11-5 06:10:45 | 显示全部楼层 |阅读模式
  我这里不实现JDK的java.util.concurrent.locks.Lock接口,而是自定义一个,因为JDK的有个newCondition()方法我这里暂时没实现。这个Lock提供了5个lock方法的变体,可以自行选择使用哪一个来获取锁,我的想法是
  最好用带超时返回的那几个方法,因为不这样的话,假如redis挂了,线程永远都在那死循环了(关于这里,应该还可以进一步优化,如果redis挂了,Jedis的操作肯定会抛异常之类的,可以定义个机制让redis挂了的时候通知使用这个lock的用户,或者说是线程)
  Java代码 下载

  •   package cc.lixiaohui.lock;

  •   import java.util.concurrent.TimeUnit;

  •   public interface Lock {

  •   /**
  •   * 阻塞性的获取锁, 不响应中断
  •   */
  •   void lock();

  •   /**
  •   * 阻塞性的获取锁, 响应中断
  •   *
  •   * @throws InterruptedException
  •   */
  •   void lockInterruptibly() throws InterruptedException;

  •   /**
  •   * 尝试获取锁, 获取不到立即返回, 不阻塞
  •   */
  •   boolean tryLock();

  •   /**
  •   * 超时自动返回的阻塞性的获取锁, 不响应中断
  •   *
  •   * @param time
  •   * @param unit
  •   * @return {@code true} 若成功获取到锁, {@code false} 若在指定时间内未获取到锁
  •   *
  •   */
  •   boolean tryLock(long time, TimeUnit unit);

  •   /**
  •   * 超时自动返回的阻塞性的获取锁, 响应中断
  •   *
  •   * @param time
  •   * @param unit
  •   * @return {@code true} 若成功获取到锁, {@code false} 若在指定时间内未获取到锁
  •   * @throws InterruptedException 在尝试获取锁的当前线程被中断
  •   */
  •   boolean tryLockInterruptibly(long time, TimeUnit unit) throws InterruptedException;

  •   /**
  •   * 释放锁
  •   */
  •   void unlock();

  •   }
  看其抽象实现:
  Java代码 下载

  •   package cc.lixiaohui.lock;

  •   import java.util.concurrent.TimeUnit;

  •   /**
  •   * 锁的骨架实现, 真正的获取锁的步骤由子类去实现.
  •   *
  •   * @author lixiaohui
  •   *
  •   */
  •   public abstract class AbstractLock implements Lock {

  •   /**
  •   *
  •   * 这里需不需要保证可见性值得讨论, 因为是分布式的锁,
  •   * 1.同一个jvm的多个线程使用不同的锁对象其实也是可以的, 这种情况下不需要保证可见性
  •   * 2.同一个jvm的多个线程使用同一个锁对象, 那可见性就必须要保证了.
  •   *
  •   */
  •   protected volatile boolean locked;

  •   /**
  •   * 当前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 {
  •   lock(false, 0, null, true);
  •   }

  •   public boolean tryLock(long time, TimeUnit unit) {
  •   try {
  •   return lock(true, time, unit, false);
  •   } catch (InterruptedException e) {
  •   // TODO ignore
  •   }
  •   return false;
  •   }

  •   public boolean tryLockInterruptibly(long time, TimeUnit unit) throws InterruptedException {
  •   return lock(true, time, unit, true);
  •   }

  •   public void unlock() {
  •   // TODO 检查当前线程是否持有锁
  •   if (Thread.currentThread() != getExclusiveOwnerThread()) {
  •   throw new IllegalMonitorStateException("current 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();

  •   /**
  •   * 阻塞式获取锁的实现
  •   *
  •   * @param useTimeout
  •   * @param time
  •   * @param unit
  •   * @param interrupt 是否响应中断
  •   * @return
  •   * @throws InterruptedException
  •   */
  •   protected abstract boolean lock(boolean useTimeout, long time, TimeUnit unit, boolean interrupt) throws InterruptedException;

  •   }
  基于Redis的最终实现,关键的获取锁,释放锁的代码在这个类的lock方法和unlock0方法里,大家可以只看这两个方法然后完全自己写一个:
  Java代码 下载

  •   package cc.lixiaohui.lock;

  •   import java.util.concurrent.TimeUnit;

  •   import redis.clients.jedis.Jedis;

  •   /**
  •   *
  •   * 基于Redis的SETNX操作实现的分布式锁
  •   *
  •   * 获取锁时最好用lock(long time, TimeUnit unit), 以免网路问题而导致线程一直阻塞
  •   *
  •   * SETNC操作参考资料
  •   *
  •   *
  •   * @author lixiaohui
  •   *
  •   */
  •   public class RedisBasedDistributedLock extends AbstractLock {

  •   private Jedis jedis;

  •   // 锁的名字
  •   protected String lockKey;

  •   // 锁的有效时长(毫秒)
  •   protected long lockExpires;

  •   public RedisBasedDistributedLock(Jedis jedis, String lockKey, long lockExpires) {
  •   this.jedis = jedis;
  •   this.lockKey = lockKey;
  •   this.lockExpires = lockExpires;
  •   }

  •   // 阻塞式获取锁的实现
  •   protected boolean lock(boolean useTimeout, long time, TimeUnit unit, boolean interrupt) throws InterruptedException{
  •   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;//锁超时时间
  •   String stringOfLockExpireTime = String.valueOf(lockExpireTime);

  •   if (jedis.setnx(lockKey, stringOfLockExpireTime) == 1) { // 获取到锁
  •   // TODO 成功获取到锁, 设置相关标识
  •   locked = true;
  •   setExclusiveOwnerThread(Thread.currentThread());
  •   return true;
  •   }

  •   String value = jedis.get(lockKey);
  •   if (value != null && isTimeExpired(value)) { // lock is expired
  •   // 假设多个线程(非单jvm)同时走到这里
  •   String oldValue = jedis.getSet(lockKey, stringOfLockExpireTime); // getset is atomic
  •   // 但是走到这里时每个线程拿到的oldValue肯定不可能一样(因为getset是原子性的)
  •   // 加入拿到的oldValue依然是expired的,那么就说明拿到锁了
  •   if (oldValue != null && isTimeExpired(oldValue)) {
  •   // TODO 成功获取到锁, 设置相关标识
  •   locked = true;
  •   setExclusiveOwnerThread(Thread.currentThread());
  •   return true;
  •   }
  •   } else {
  •   // TODO lock is not expired, enter next loop retrying
  •   }
  •   }
  •   return false;
  •   }

  •   public boolean tryLock() {
  •   long lockExpireTime = System.currentTimeMillis() + lockExpires + 1;//锁超时时间
  •   String stringOfLockExpireTime = String.valueOf(lockExpireTime);

  •   if (jedis.setnx(lockKey, stringOfLockExpireTime) == 1) { // 获取到锁
  •   // TODO 成功获取到锁, 设置相关标识
  •   locked = true;
  •   setExclusiveOwnerThread(Thread.currentThread());
  •   return true;
  •   }

  •   String value = jedis.get(lockKey);
  •   if (value != null && isTimeExpired(value)) { // lock is expired
  •   // 假设多个线程(非单jvm)同时走到这里
  •   String oldValue = jedis.getSet(lockKey, stringOfLockExpireTime); // getset is atomic
  •   // 但是走到这里时每个线程拿到的oldValue肯定不可能一样(因为getset是原子性的)
  •   // 假如拿到的oldValue依然是expired的,那么就说明拿到锁了
  •   if (oldValue != null && isTimeExpired(oldValue)) {
  •   // TODO 成功获取到锁, 设置相关标识
  •   locked = true;
  •   setExclusiveOwnerThread(Thread.currentThread());
  •   return true;
  •   }
  •   } else {
  •   // TODO lock is not expired, enter next loop retrying
  •   }

  •   return false;
  •   }

  •   /**
  •   * Queries if this lock is 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 这里其实是有问题的, 想:当get方法返回value后, 假设这个value已经是过期的了,
  •   // 而就在这瞬间, 另一个节点set了value, 这时锁是被别的线程(节点持有), 而接下来的判断
  •   // 是检测不出这种情况的.不过这个问题应该不会导致其它的问题出现, 因为这个方法的目的本来就
  •   // 不是同步控制, 它只是一种锁状态的报告.
  •   return !isTimeExpired(value);
  •   }
  •   }

  •   @Override
  •   protected void unlock0() {
  •   // TODO 判断锁是否过期
  •   String value = jedis.get(lockKey);
  •   if (!isTimeExpired(value)) {
  •   doUnlock();
  •   }
  •   }

  •   private void checkInterruption() throws InterruptedException {
  •   if(Thread.currentThread().isInterrupted()) {
  •   throw new InterruptedException();
  •   }
  •   }

  •   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);
  •   }

  •   }
  如果将来还换一种实现方式(比如zookeeper之类的),到时直接继承AbstractLock并实现lock(boolean useTimeout, long time, TimeUnit unit, boolean interrupt), unlock0()方法即可(所谓抽象嘛)



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-630775-1-1.html 上篇帖子: redis演练(2) 最全redis命令列表 下篇帖子: redis演练(3) redis事务管理
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表