Redis(十三)Redis使用setnx实现分布式锁

前言

对同一个值进行修改:

如果是单机,线程之间共享内存,只要使用synchronized线程锁就可以解决并发情况下值的一致性问题。

如果是分布式情况,并发的线程A和线程B不在同一JVM(程序)中,他们之间的synchronized没有相互作用,这样synchronized线程锁就无效了,这时候就要用到分布式锁来解决,这里我们采用Redis设置关键字(flag)来标记是否锁住。

值得注意的是

  1. 同一时刻只能有一个线程获取一个值;
  2. 防止某一线程死锁的情况导致锁不被释放(设置锁存活时间);

原理

setnx(key, value):“set if not exits”,若该key-value不存在,则成功加入缓存并且返回1,否则返回0。据此判断键值对是否占用锁住。

		System.out.println(jedis.setnx(lockTitle, "locked"));  //1
		System.out.println(jedis.setnx(lockTitle, "locked"));  //0

 

未加锁的情况

设置六个线程消费一个钱包KEY:初始化100【代码是加锁情况下的注解】

Redis(十三)Redis使用setnx实现分布式锁_第1张图片

 

加大消费线程数100:

击穿缓存。 【线程名是线程池里面的名称,线程池中的线程进进出出,固定为6】

Redis(十三)Redis使用setnx实现分布式锁_第2张图片

 

释放jedis

记得要释放jedis要不然会超出jedis连接池的数量。

Redis(十三)Redis使用setnx实现分布式锁_第3张图片

加锁

public class RedisLock {

	private static final String LOCK_TITLE = "redisLock_wallet";
	private static final String KEY = "wallet";
	private static final int THREAD_NUM = 100;
	public static void main(String[] args) {
		Jedis jedis = JedisPoolDemo.initJedisPool().getResource();

		jedis.set(KEY, "100");
		jedis.del(LOCK_TITLE);

		Executor threadExecutor = Executors.newFixedThreadPool(6);
		for (int i = 0; i < THREAD_NUM; i++) {
			threadExecutor.execute(new WalletConsumer());
		}
		
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("最终钱包还有:" + jedis.get(KEY));
	}

	
	
	//消费线程
	static class WalletConsumer implements Runnable {
		@Override
		public void run() {
			// TODO Auto-generated method stub
			// 线程私有
			Jedis jedis = JedisPoolDemo.getJedis();

			//一直请求获取锁,直到获取锁		
			while (true) {
				if (jedis.setnx(LOCK_TITLE, "locked") == 1) { // 未上锁就上锁
					
					int wallet =Integer.valueOf(jedis.get(KEY)) - 1;
					jedis.set(KEY, String.valueOf(wallet));
					System.err.println(Thread.currentThread().getName() + "消费了钱包" + wallet);
					break;
				}else {
					System.err.println("被锁住");
				}
			}

			//释放锁
			jedis.close();
			jedis.del(LOCK_TITLE);

		}
	}

}

Redis(十三)Redis使用setnx实现分布式锁_第4张图片

不足

上述情况下,如果有线程(用户)A在释放锁前,获取锁后如果有一线程死锁不释放锁,那该怎么办呢?

			//释放锁
			jedis.close();
			if (Integer.valueOf(jedis.get(KEY)) != 66) {
				jedis.del(LOCK_TITLE);
			}

Redis(十三)Redis使用setnx实现分布式锁_第5张图片

设置过期时间

				if (jedis.setnx(LOCK_TITLE, "locked") == 1) { // 未上锁就上锁
					jedis.expire(LOCK_TITLE, 6);

Redis(十三)Redis使用setnx实现分布式锁_第6张图片

PS

出现下面错误的原因是:多个线程使用同一个jedis导致的。所以上面最后消费还剩下1。

Exception in thread "pool-1-thread-5" java.lang.ClassCastException: [B cannot be cast to java.lang.Long

Redis分布式锁RedLock的Java实现(解锁只解当前线程上锁的锁):Redis(十四)Redisson由简到消费实例分析和实现

参考文章

并发类转型错误原因

你可能感兴趣的:(Redis,redis,java,并发,缓存,setnx)