Java利用Redis实现分布式锁(核心代码实现)

        摘要:读完本章您将知道如何利用Redis设计分布式锁

        在Java中利用Redis实现分布式锁主要利用jedis API(Jedis类) 提供的以下几个方法:

        Long setnx(final byte[] key, final byte[] value);

        往redis服务器中设置值,如果key已存在,设置失败返回0;如果key不存在,设置成功返回1。

        Long expire(final byte[] key, final int seconds);

        给值key设置过期时间单位是秒;

        Long ttl(final byte[] key);

        获取值key的过期时间单位秒,如果key不存在返回-2,如果key存在但未设置过期时间返回-1。

        Long del(final byte[] key);

        移除值key。

        需要用到锁的场景:

        对象会被多个线程操作,线程之间的读写操作会互相影响,这样必须保证一个线程操作完之后,另外一个线程才能操作该对象。比如这样一个场景,你办了一张银行卡,同时这张卡关联了一张存折,卡和存折都可以存钱取钱,卡在你手上,存折在你媳妇(假装有媳妇)手里,你往账户存5000元的同时你媳妇去柜台取5000元,原本账户有5000元。假设存钱的动作是先获取账户的钱,然后再加上存进去的钱,再往库里写;取钱的动作是先获取账户的钱,然后再减去取的钱,最后将值重新写到库中,假设没有锁,获取账户的动作是同时的,也就是这两个动作获取的值是一致的,最后导致的结果就是你的账户余额可能是(5000+5000)元或者5000-5000=0元。而这两个结果都不是你跟银行双方想要的,这时候咱们就需要加一把锁,存钱之前先获取锁,存完之后再释放锁,同样取钱也是,取钱的动作之前先获取锁,成功之后再释放锁。

        业务代码模拟:

// 获取锁
		Lock lock = this.cache.getLock(key);
		try {
			//业务代码 存钱或者取钱
		} finally {
			// 释放锁,必须写在finally里,避免业务代码抛出异常导致锁没及时被释放
			this.cache.unLock(lock);
		}

         Redis获取锁核心代码:

public Lock getLock(String key) {
		return this.getLock(key,-1L);
	}
	
	public Lock getLock(String key,long timeout) {
		return this.getLock(key,System.currentTimeMillis(),timeout);
	}
	
	private final Lock getLock(String key,final long createTime,final long timeout) {
		if (StringUtils.isEmpty(key)) {
			return null;
		}
		// 用Lock封装了一些基础属性,如id、 key、createTime
		// 提供了一些基础方法,如isExpire,默认过期时间为6小时
		Lock lock = new Lock(key);
		while (true) {
			// 调用自定义setNx方法,如果设置成功返回true
			if (this.setNx(lock.getKey(),lock)) {
				// 手动设置过期时间10分钟,避免锁长时间未释放
				this.expire(lock.getKey(),Cache.MINUTE * 10);
				return lock;
			}
			// 如果值未设置成功,获取旧的值Lock对象
			Lock oldLock = this.get(lock.getKey(),Lock.class);
			if (null == oldLock) {
				continue;
			}
			// ttl方法获取值的剩余时间,如果返回-2,表示该值不存在;如果返回-1,表示该值存在但未设置过期时间
			long expireTime = this.ttl(lock.getKey());
			if (expireTime == -2L) {
				continue;
			}
			// 如果值存在,而且已到了默认的6小时过期时间,则手动释放
			if (expireTime == -1L && oldLock.isExpire()) {
				this.unLock(oldLock);
				continue;
			}
			// 如果还剩0继续循环,下一次值能获取成功,即成功获取到锁
			if (expireTime == 0L) {
				continue;
			}
			// 如果想设置的剩余时间大于0,但设置的时间加上创建时间小于当前时间,则返回null,这种值设置成功无意义
			if (timeout > 0L && timeout + createTime <= System.currentTimeMillis()) {
				return null;
			}
			if (timeout == 0L) {
				return null;
			}
			try {
				// 如果第一次未获取到锁,适当让休眠一段时间,减少cpu的轮询次数,提高性能
				Thread.sleep(Math.min(timeout > 0L ? timeout:60L,60L));
			} catch (InterruptedException e) {
				throw new RuntimeException(e.getMessage(),e);
			}
		}
	}

        Redis 释放锁代码:

public void unLock(Lock lock) {
		if (null == lock) {
			return;
		}
		if (null == lock.getKey()) {
			return;
		}
		Lock oldLock = this.get(lock.getKey(),Lock.class);
		if (null == oldLock) {
			return;
		}
		if (!lock.equals(oldLock)) {
			return;
		}
		// 移除值
		this.remove(lock.getKey());
	}

        Lock对象的isExpire的实现方法是根据创建时间跟当前时间进行比较,如果当前时间减创建时间大于6个小时,则返回true.  

猜你喜欢

转载自blog.csdn.net/yy455363056/article/details/80309905