java实现redis分布式锁,已经过实际线上环境测试

这两天重构之前的项目线上测试,发现线程池总是很快就满了,打印了线程池日志,发现报错提示数据库死锁,查看数据库日志定位到具体的sql和数据表,发现该表有五个地方修改,纠结之下暂时放弃了事务处理;然后上线,运行一段时间后仍然是会有部分线程长时间获取不到锁。仔细分析设计到这部分的代码,包括锁的实现,并简单修改了下,上线后未出现问题。

今天打算重新整理下,偶然实现之前的代码和网上的部分博客完全一样!!!!


import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Service;

/**
 * 加锁内的操作应该只是一些简单的判断,读取等操作,尽量在LOCK_TIMEOUT(5s)内执行结束,
 * 否则超时的话,锁会自动消失,导致并发问题
 * 超时设定本身在解决程序关闭,重启等再次请求时锁的初始状态,而非处理业务超时的问题
 * @author f
 *
 */
@Service
public class RedisLockImpl implements RedisDistributionLock {
	// 加锁超时时间,单位毫秒, 即:加锁时间内执行完操作,如果未完成会有并发现象
	private static final long LOCK_TIMEOUT = 5 * 1000;

	private static final Logger LOG = LoggerFactory.getLogger(RedisLockImpl.class);
	@Autowired
	private StringRedisTemplate redisTemplate;

	/**
	 * 加锁 取到锁加锁,取不到锁一直等待知道获得锁
	 * 
	 * @param lockKey
	 * @param threadName
	 * @return
	 */
	@Override
	public synchronized long lock(String lockKey, String threadName) {
		LOG.info(threadName+"开始执行加锁");
		//循环获取锁
		while (true) {
			// 锁超时时间
			Long lock_timeout = currtTimeForRedis() + LOCK_TIMEOUT + 1;
			Boolean execute = redisTemplate.execute(new RedisCallback<Boolean>() {
				@Override
				public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
					RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
					byte[] value = serializer.serialize(lock_timeout.toString());
					boolean flag = redisConnection.setNX(lockKey.getBytes(), value);
					return flag;
				}
			});
			if (execute) {
				LOG.info(threadName +"加锁成功 ++++ 111111");
				redisTemplate.expire(lockKey, LOCK_TIMEOUT * 2, TimeUnit.MILLISECONDS);
				return lock_timeout;
			} else {
				// 获取上一个锁的时间
				String lastLockTimeStr = redisTemplate.opsForValue().get(lockKey);
				Long lastLockTime = lastLockTimeStr == null ? null : Long.parseLong(lastLockTimeStr);
                //当前时间和上一个锁的到期时间做比较
				long timeGap = lastLockTime - currtTimeForRedis();
				LOG.info("lastLockTime:"+lastLockTime+"---"+timeGap);
				// 锁已经失效
				if (lastLockTime != null && timeGap < 0) {
					// 判断是否为空,不为空时且时间差值小于0,说明已经失效,如果被其他线程设置了值,则第二个条件判断无法执行
					// 获取上一个锁到期时间,并设置现在的锁到期时间,通过这个原子操作,避免多线程竞争
					Long lastLockTimeStr2 = Long.valueOf(redisTemplate.opsForValue().getAndSet(lockKey, lock_timeout.toString()));
					LOG.info("lastLockTimeStr:"+lastLockTimeStr+"lastLockTimeStr2:"+lastLockTimeStr2);
					if (lastLockTimeStr2 != null && lastLockTimeStr2.equals(lastLockTimeStr)) {
						// 多线程运行时,多个线程恰好都到了这里,通过返回的锁超时时间判断是否加锁成功
						LOG.info(threadName + "加锁成功 ++++ 22222");
						redisTemplate.expire(lockKey, LOCK_TIMEOUT, TimeUnit.MILLISECONDS);
						return lock_timeout;
					}
				}
			}
			try {
				LOG.info(threadName +"等待加锁, 睡眠200毫秒");
				TimeUnit.MILLISECONDS.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}


	/**
	 * 解锁
	 * 这里不需要synchronized,方法级是class对象加锁
	 * @param lockKey
	 * @param lockValue
	 * @param threadName
	 */
	@Override
	public void unlock(String lockKey, long lockValue, String threadName) {
		LOG.info(threadName + "执行解锁==========");//正常直接删除 如果异常关闭判断加锁会判断过期时间
		// 获取redis中设置的时间
		String result = redisTemplate.opsForValue().get(lockKey);
		Long currt_lock_timeout_str = result == null ? null : Long.valueOf(result);

		// 如果是加锁者,则删除锁, 如果不是,则等待自动过期,重新竞争加锁
		if (currt_lock_timeout_str != null && currt_lock_timeout_str == lockValue) {
			redisTemplate.delete(lockKey);
			LOG.info(threadName + "解锁成功------------------");
		}else {
			LOG.info(threadName + "锁已被替换------------------");
		}
	}

	/**
	 * 多服务器集群,使用下面的方法,代替System.currentTimeMillis(),获取redis时间,避免多服务的时间不一致问题!!!
	 * 
	 * @return
	 */
	@Override
	public long currtTimeForRedis() {
		return redisTemplate.execute(new RedisCallback<Long>() {
			@Override
			public Long doInRedis(RedisConnection redisConnection) throws DataAccessException {
				return redisConnection.time();
			}
		});
	}
}
public interface RedisDistributionLock {
	/** 
	   * 加锁成功,返回加锁时间 
	   * @param lockKey 
	   * @param threadName 
	   * @return 
	   */ 
	  public long lock(String lockKey, String threadName); 
	  
	  /** 
	   * 解锁, 需要更新加锁时间,判断是否有权限 
	   * @param lockKey 
	   * @param lockValue 
	   * @param threadName 
	   */ 
	  public void unlock(String lockKey, long lockValue, String threadName); 
	  
	  /** 
	   * 多服务器集群,使用下面的方法,代替System.currentTimeMillis(),获取redis时间,避免多服务的时间不一致问题!!! 
	   * @return 
	   */ 
	  public long currtTimeForRedis(); 
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值