Redis(单节点)实现分布式锁

在实现分布式锁前,先了解下以下内容

1 缓存有效期

Redis中的数据,不一定都是持久化的;给定key设置的生存时间,当key过期,它会被自动删除。

2 setnx 命令

setnx key value , 将key的值为value.当且仅当key不存在,若给定的key已存在,则setnx不做任何操作。setnx 是 [ set if not eXists ] (如果不存在,则set)的简写

3 lua 脚本

轻量小巧的脚本语言,用于支持Redis操作序列的原子性;

加锁

通过setnx向特定的key写入一个随机值,并同时设置有效时间,写值成功即加锁成功;

注意点:

  • 必须给锁设置一个有效时间 ,避免死锁;
  • 加锁时,每个节点产生一个随机字符串 ,避免误删锁;
  • 写入随机值与设置有效时间必须是同时的,保证加锁时原子的;

解锁

匹配随机值,删除Redis特定的key数据,要保证获取数据,判断一致以及删除数据三个操作是原子的;

执行如下lua脚本:

 if redis.call('get', KEYS[1]) == ARGV[1] then
	 return redis.call('del', KEYS[1]) 
 else 
 	return 0 
 end

首先模拟一个卖票的场景TicketTest :

package com.redis.RedisLock;

import org.junit.Test;

public class TicketTest {

	private int   count =100;
	//private AtomicInteger count = new AtomicInteger(100);
	
	@Test
	public void ticketTest() throws InterruptedException {
		TicketRunable tr=new TicketRunable();
		Thread t1=new Thread(tr,"窗口A");
		Thread t2=new Thread(tr,"窗口B");
		Thread t3=new Thread(tr,"窗口C");
		Thread t4=new Thread(tr,"窗口D");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		Thread.currentThread().join();
	}
	
	//线程模拟卖票
	public class  TicketRunable implements Runnable{
		public void run() {
			while(count>0) {
				if (count>0) {
					System.out.println(Thread.currentThread().getName()+"售出第"+(count--)+"票");
				}
				try {
					Thread.sleep(50);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			
		}
		
	}
}

运行结果如下:
在这里插入图片描述
结果没有完全贴出,但是结果明显是线程不安全的

下面实现redis分布式锁

基于这篇文章 Java 连接Redis,使用到了里面的工具类RedisUtil.java

锁的实现

public class RedisLock implements Lock{
	
	private static final String KEY="KEY";
	
	ThreadLocal<String> local = new ThreadLocal<String>();
 
	/**
	 * 阻塞式加锁
	 */
	public void lock() {
		//1 尝试加锁
		if (tryLock()) {
			return;
		}
		//2 加锁失败,当前任务休眠一段时间
		try {
			Thread.sleep(10);
		} catch (Exception e) {
			e.printStackTrace();
		}
		//3 递归调用,再次重新加锁
		lock();
		
	}

	/**
	 * 非阻塞式加锁,使用setnx命令返回OK则加锁成功,并生产随机值
	 */
	public boolean tryLock() {
		//产生随机值
		String uuid=UUID.randomUUID().toString();
		//获取redis连接
		Jedis jedis=RedisUtil.getSigleRedis();
		//使用setNx命令请求写值,并设置有效时间 (KEY,value,NX:表示使用setnx模式,PX:表示设置有效时间,有效时间)
		String ret =jedis.set(KEY, uuid, "NX", "PX", 1000);
		//返回 OK 表示 加锁成功
		if ("OK".equals(ret)) {
			local.set(uuid);
			return true;
		}
		//没返回 OK 表示加锁失败
		return false;
	}

	/**
	 * 解锁
	 */
	public void unlock() {
		//读取lua脚本
		String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
		//获取redis连接
		Jedis jedis=RedisUtil.getSigleRedis();
		//执行lua脚本
		jedis.eval(luaScript,Arrays.asList(KEY),Arrays.asList(local.get()));
		
	}

	public void lockInterruptibly() throws InterruptedException {
		// TODO Auto-generated method stub
		
	}

	
	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		// TODO Auto-generated method stub
		return false;
	}

	
	public Condition newCondition() {
		// TODO Auto-generated method stub
		return null;
	}

}

改造上面的TicketTest

public class TicketTestRedisLock {

	private int   count =100;
	private RedisLock lock=new RedisLock();
	//private AtomicInteger count = new AtomicInteger(100);
	
	@Test
	public void ticketTest() throws InterruptedException {
		TicketRunable tr=new TicketRunable();
		Thread t1=new Thread(tr,"窗口A");
		Thread t2=new Thread(tr,"窗口B");
		Thread t3=new Thread(tr,"窗口C");
		Thread t4=new Thread(tr,"窗口D");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		Thread.currentThread().join();
	}
	
	//线程模拟买票
	public class  TicketRunable implements Runnable{
		public void run() {
			while(count>0) {
				lock.lock(); //加锁
				try {
					if (count>0) {
						System.out.println(Thread.currentThread().getName()+"售出第"+(count--)+"票");
					}
				} catch (Exception e) {
					
				}finally {
					lock.unlock();//释放锁
				}
				
				try {
					Thread.sleep(50);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			
		}
		
	}
}

运行结果:

	    窗口C售出第100票
		窗口B售出第99票
		窗口D售出第98票
		窗口A售出第97票
		窗口C售出第96票
		窗口B售出第95票
		窗口D售出第94票
		窗口C售出第93票
		窗口A售出第92票
		窗口B售出第91票
		窗口D售出第90票
		窗口C售出第89票
		窗口A售出第88票
		窗口B售出第87票
		窗口C售出第86票
		窗口D售出第85票
		窗口B售出第84票
		窗口C售出第83票
		窗口A售出第82票
		窗口D售出第81票
		窗口B售出第80票
		窗口C售出第79票
		窗口A售出第78票
		窗口B售出第77票
		窗口C售出第76票
		窗口D售出第75票
		窗口A售出第74票
		窗口C售出第73票
		窗口D售出第72票
		窗口A售出第71票
		窗口B售出第70票
		窗口D售出第69票
		窗口A售出第68票
		窗口C售出第67票
		窗口B售出第66票
		窗口D售出第65票
		窗口A售出第64票
		窗口C售出第63票
		窗口D售出第62票
		窗口B售出第61票
		窗口A售出第60票
		窗口C售出第59票
		窗口D售出第58票
		窗口B售出第57票
		窗口A售出第56票
		窗口C售出第55票
		窗口D售出第54票
		窗口B售出第53票
		窗口A售出第52票
		窗口C售出第51票
		窗口B售出第50票
		窗口D售出第49票
		窗口A售出第48票
		窗口C售出第47票
		窗口B售出第46票
		窗口A售出第45票
		窗口D售出第44票
		窗口C售出第43票
		窗口B售出第42票
		窗口A售出第41票
		窗口D售出第40票
		窗口C售出第39票
		窗口B售出第38票
		窗口A售出第37票
		窗口D售出第36票
		窗口C售出第35票
		窗口B售出第34票
		窗口A售出第33票
		窗口D售出第32票
		窗口C售出第31票
		窗口B售出第30票
		窗口A售出第29票
		窗口C售出第28票
		窗口D售出第27票
		窗口A售出第26票
		窗口B售出第25票
		窗口C售出第24票
		窗口A售出第23票
		窗口D售出第22票
		窗口B售出第21票
		窗口C售出第20票
		窗口A售出第19票
		窗口D售出第18票
		窗口B售出第17票
		窗口C售出第16票
		窗口A售出第15票
		窗口D售出第14票
		窗口B售出第13票
		窗口C售出第12票
		窗口A售出第11票
		窗口B售出第10票
		窗口D售出第9票
		窗口C售出第8票
		窗口A售出第7票
		窗口B售出第6票
		窗口D售出第5票
		窗口C售出第4票
		窗口A售出第3票
		窗口B售出第2票
		窗口D售出第1票
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值