今天工作之余,查看一下利用redis来实现分布式锁,因此,在查看别人文章之余,自己也来手动模拟实现Java的lock接口,来自己手动实现一个分布式锁。拥有简单的加锁,解锁,锁中断等操作。
利用redis的分布式锁,主要还是利用redis的setnx命令,查看redis文档,可知次命令在redis缓存中添加数据的时候,如果key存在,则添加数据操作不成功。若不存在,才可以添加成功。从另外一个方面来理解锁(Lock),其实就是一种资源,在某个时刻标记为只能被某个线程使用,若资源已经被使用,则其他线程必须等待当前线程释放资源之后才可以使用。从这个方面理解,也就是,当前线程持有在redis缓存中key的资源,所以其他线程必须等待当前线程释放key的资源,否则只能等待。下面来贴上代码具体分析下:
- package com.redislock;
- import redis.clients.jedis.Jedis;
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.locks.Condition;
- import java.util.concurrent.locks.Lock;
- /**
- * 尝试使用redis实现分布式锁
- * Created by hadoop on 2017/3/16.
- */
- public class RedisLock implements Lock{
- /**jedis客户端**/
- private final Jedis jedis;
- /**锁定资源的key**/
- private final String lockName;
- /**持有锁的最长时间**/
- private final int expireTime = 300;
- /**获取不到锁的休眠时间**/
- private final long sleepTime = 100;
- /**锁中断状态**/
- private boolean interruped = true;
- /**超时时间**/
- private long expireTimeOut = 0;
- public RedisLock(Jedis jedis, String lockName){
- this.jedis = jedis;
- this.lockName = lockName;
- }
- public void lock() {
- if (jedis == null)
- throw new NullPointerException("jedis is null");
- if (lockName == null)
- throw new NullPointerException("key is null");
- while (true){
- if (!interruped)
- throw new RuntimeException("获取锁状态被中断");
- long id = jedis.setnx(lockName, lockName);
- if (id == 0L){
- try {
- Thread.currentThread().sleep(this.sleepTime);
- }catch (InterruptedException e){
- e.printStackTrace();
- }
- }else{
- expireTimeOut = System.currentTimeMillis()/1000 + expireTime;
- jedis.expireAt(this.lockName, expireTimeOut);
- break;
- }
- }
- }
- public void lockInterruptibly() throws InterruptedException {
- this.interruped = false;
- }
- public boolean tryLock() {
- if (jedis == null)
- throw new NullPointerException("jedis is null");
- if (lockName == null)
- throw new NullPointerException("lockName is null");
- if (!interruped)
- throw new RuntimeException("线程被中断");
- long id = jedis.setnx(lockName, lockName);
- if (id == 0L)
- return false;
- else {
- // 设置锁过期时间
- expireTimeOut = System.currentTimeMillis()/1000 + expireTime;
- jedis.expireAt(this.lockName, expireTimeOut);
- return true;
- }
- }
- public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
- if (jedis == null)
- throw new NullPointerException("jedis is null");
- if (lockName == null)
- throw new NullPointerException("lockName is null");
- if (time == 0)
- return false;
- long now = System.currentTimeMillis();
- long timeOutAt = now + calcSeconds(time, unit);
- while (true){
- if (!interruped)
- throw new InterruptedException("线程被中断");
- long id = jedis.setnx(this.lockName, this.lockName);
- // id = 0 表示加锁失败
- if(id == 0){
- // 获取锁超时
- if(System.currentTimeMillis() > timeOutAt)
- return false;
- try {
- // 休眠一段时间,继续获取锁
- Thread.currentThread().sleep(this.sleepTime);
- }catch (InterruptedException e){
- e.printStackTrace();
- }
- }else {
- //获取锁成功,设置锁过期时间
- expireTimeOut = System.currentTimeMillis()/1000 + expireTime;
- jedis.expireAt(this.lockName, expireTimeOut);
- return true;
- }
- }
- }
- public void unlock() {
- try {
- //当前时间小于过期时间,则锁未超时,删除锁定
- if (System.currentTimeMillis() / 1000 < expireTimeOut)
- jedis.del(lockName);
- }catch (Exception e){
- }finally {
- jedis.close();
- }
- }
- public Condition newCondition() {
- throw new UnsupportedOperationException("不支持当前的操作");
- }
- /**
- * 时间转换成毫秒
- * @param time
- * @param unit
- * @return
- */
- private long calcSeconds (long time, TimeUnit unit){
- if (unit == TimeUnit.DAYS)
- return time * 24 * 60 * 60 * 1000;
- else if (unit == TimeUnit.HOURS)
- return time * 60 * 60 * 1000;
- else if (unit == TimeUnit.MINUTES)
- return time * 60 * 1000;
- else
- return time * 1000;
- }
在这里,说两点,第一点就是redis key的超时,这样可以保证在客户端程序突然down的时候,资源可以在一段时间之后被释放掉,不会产生死锁。还有就是unlock()这个方法,在加锁的时候,会计算锁释放的时间,在释放锁的时候,要判定当前锁是否超时了,若超时,怎不能删除key。否则会破坏线程的安全性。因为不用的redis客户端,虽然不能setnx key值,但是可以del这个key。当然,我这里是一种最简单的处理,在临界条件下也容易出现问题,只是目前可以想到的一种办法。也希望大神们不吝赐教小弟我。
第二点需要说明的是中断状态,基于以前看多线程安全结束的模式,在此处我也是设置标志量interruped 用来标识当前的锁已经被中断,在进行任何操作前,都会先判定这个中断标志,若被中断,则抛出程序异常。 目前还不知道这样做到底会如何,希望路过大神给指导一下意见!!!完全自学,希望不吝赐教。在写的过程中,考虑到一件事情,若中断标志被改变,也就是锁被中断,是否需要调用unlock()方法释放这个锁???