基于redis的分布式锁(按顺序拿锁和释放)

之前知道可以通过redis的setnx来进行加锁,但是,获取锁的顺序是没有保障的。刚好之前有写过一个AQS的博客,所以基于那个队列的原理写一个非公平分布式锁。

主要原理是,每个想获取锁的节点需要通过以下步骤(若获取锁user):
1.拿到序列号(user:seq)
2.入等待队列(队列为user:list)。
3.轮询,若队列头部为自己,则去获取锁。

主要接口为以下,使用方法与ReentreenLock类似,但是需要传入加锁的key:

public class TestLock {
	@Autowired
	private  FenbushiLock lock;//分布式锁
	
	public void get() throws InterruptedException{
 		lock.lock("user");
	}
	
	public void unlock() throws InterruptedException{
		lock.unlock("user");
	}
	
}

锁内部实现代码为以下:

public void lock(String key) throws InterruptedException {
	//	Boolean result = redisUtil.setNx(key);
		final String listKey = key+":list";//该锁配合使用的队列
		final String seqKey = key+":seq";//该锁配合使用的唯一序列。
		
			redisTemplate.watch(seqKey);//监控序列key,防止被人改变。
			Object key2 = redisTemplate.boundValueOps(seqKey).get();
			if(key2==null){//若为空,说明队列中无数据。
				key2 = 0;
			}
			long trySeq = Long.parseLong(key2.toString())+1;//试图设置序列为当前序列+1
			boolean same = false;
			while(!same){
				redisTemplate.multi();//开始事务,这个事务可以保证,拿到的序列号马上和如队列排队的号为一致的,防止拿到了序列3,但是因为非原子性,导致排在了序列4的后面。
				redisTemplate.boundValueOps(seqKey).increment(1);//序列加1
				redisTemplate.boundListOps(listKey).rightPush(trySeq);//将序列入队
			    List<Object> l= redisTemplate.exec();
			    if(l.size()!=0){//说明执行成功
			    	break;//当break时,说明成功拿到序号,并入队列等待。
			    }else{
			    	redisTemplate.watch(seqKey);//否则说明已经有人先拿了序列,并且入队。
			    	trySeq = (Long) redisTemplate.boundValueOps(seqKey).get()+1;//重新试图设置序列为当前序列+1。
			    }
			}
			
			long top = redisUtil.listGet(listKey);//当前排队的最前面的节点。
			for(;;){
				if(top!=trySeq){//如果最前面的节点不是当前节点
					try {
						Thread.sleep(1);//sleep一下,然后继续循环
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					top = redisUtil.listGet(listKey);//重新获取最前面的节点。
				}else{//如果当前节点已经是最top的节点,则试着setnx拿锁。
					boolean result = redisUtil.setNx(key);
					if(result){//如果拿锁成功,则打印日志。
						System.out.println("序号"+trySeq+"获取到锁:");
						return ;
					}
				}
			}
	}
public void unlock(String key) {
		redisUtil.listLPop(key+":list");//释放锁时,将最top元素pop出队列。
		redisUtil.removeKey(key);//并移除锁。
}

test函数如下:

@SpringBootApplication
@EnableTransactionManagement
public class ServerStart {
	public static void main(String args[]){
		ApplicationContext a =SpringApplication.run(ServerStart.class, args);
		final TestLock t = (TestLock) a.getBean(TestLock.class);
		for(int i=0;i<100;i++){//100个线程获取锁,按顺序获取和释放。
			new Thread(new Runnable() {
				public void run() {
					try {
						t.get();
						t.unlock();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}).start();
		}
	}
}

结果(此为单机结果,可以多节点使用):
在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值