分布式锁能够保证在多线程访问共享资源时,不会在同一时间内访问相同的资源。
原子操作是不需要synchronized的,因为原子操作是不会被线程调度机制打断的:这种操作一旦开始,就一直运行到结束,中间不会有任何线程切换。
使用Redis中的setnx属性,如果setnx返回的是1,表示获取锁成功;反之,返回0,表示该资源有人正在编辑,无法使用,操作完成后,不管有没有异常,都要释放锁。
- redis配置文件
redis.hostName=127.0.0.1 redis.port=6379 # 连接超时时间 redis.timeout=10000 #最大空闲数 redis.maxIdle=300 #控制一个pool可分配多少个jedis实例,用来替换上面的redis.maxActive,如果是jedis 2.4以后用该属性 redis.maxTotal=1000 #最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。 redis.maxWaitMillis=1000 #连接的最小空闲时间 默认1800000毫秒(30分钟) redis.minEvictableIdleTimeMillis=300000 #每次释放连接的最大数目,默认3 redis.numTestsPerEvictionRun=1024 #逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1 redis.timeBetweenEvictionRunsMillis=30000 #是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个 redis.testOnBorrow=true #在空闲时检查有效性, 默认false redis.testWhileIdle=true
-
加载Redis配置文件,将RedisTemplate对象纳入spring容器。该文件启动项目的时候加载。
package com.daling.util.lock.redisTemplateLock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.jcache.config.JCacheConfigurerSupport; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.core.env.Environment; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; @Configuration @PropertySource("classpath:redisTemplate.properties") public class RedisConfig extends JCacheConfigurerSupport { @Autowired private Environment environment; @Bean public RedisConnectionFactory redisConnectionFactory() { JedisConnectionFactory fac = new JedisConnectionFactory(); fac.setHostName(environment.getProperty("redis.hostName")); fac.setPort(Integer.parseInt(environment.getProperty("redis.port"))); fac.setTimeout(Integer.parseInt(environment.getProperty("redis.timeout"))); fac.getPoolConfig().setMaxIdle(Integer.parseInt(environment.getProperty("redis.maxIdle"))); fac.getPoolConfig().setMaxTotal(Integer.parseInt(environment.getProperty("redis.maxTotal"))); fac.getPoolConfig().setMaxWaitMillis(Integer.parseInt(environment.getProperty("redis.maxWaitMillis"))); fac.getPoolConfig().setMinEvictableIdleTimeMillis( Integer.parseInt(environment.getProperty("redis.minEvictableIdleTimeMillis"))); fac.getPoolConfig().setNumTestsPerEvictionRun(Integer.parseInt(environment.getProperty("redis.numTestsPerEvictionRun"))); fac.getPoolConfig().setTimeBetweenEvictionRunsMillis( Integer.parseInt(environment.getProperty("redis.timeBetweenEvictionRunsMillis"))); fac.getPoolConfig().setTestOnBorrow(Boolean.parseBoolean(environment.getProperty("redis.testOnBorrow"))); fac.getPoolConfig().setTestWhileIdle(Boolean.parseBoolean(environment.getProperty("redis.testWhileIdle"))); return fac; } @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Object> redis = new RedisTemplate<>(); redis.setConnectionFactory(redisConnectionFactory); redis.afterPropertiesSet(); return redis; } }
3,Redis分布式锁的具体实现,包含加锁和释放锁
package com.daling.util.lock.redisTemplateLock;
import org.springframework.data.redis.core.RedisTemplate;
import java.io.Closeable;
import java.io.IOException;
public class RedisLock implements Closeable {
private RedisTemplate<String,Object> redisTemplate;
/**
* 重试时间
*/
private static final int DEFAULT_ACQUIRY_RETRY_MILLIS = 100;
/**
* 锁的前缀
*/
private final String LOCK_PREFFIX = "redisTemplate_";
/**
* 锁的key
*/
private String lockKey;
/**
* 锁失效时间,防止线程在入锁以后,阻塞后面的线程无法获取锁
*/
private int expireMsecs = 60 * 1000;
// private int expireMsecs = 1 * 1000;
/**
* 线程获取锁的等待时间60s
*/
private int timeoutMsecs = 60 * 1000;
/**
* 是否锁定标志
*/
private boolean locked = false;
/**
* 构造器
* @param redisTemplate
* @param lockKey 锁的key
*/
public RedisLock(RedisTemplate<String,Object> redisTemplate, String lockKey) {
this.redisTemplate = redisTemplate;
this.lockKey = LOCK_PREFFIX + lockKey;
}
/**
* 构造器
* @param redisTemplate
* @param lockKey 锁的key
* @param timeoutMsecs 获取锁的超时时间
*/
public RedisLock(RedisTemplate<String,Object> redisTemplate, String lockKey, int timeoutMsecs) {
this(redisTemplate, lockKey);
this.timeoutMsecs = timeoutMsecs;
}
/**
* 构造器
* @param redisTemplate
* @param lockKey 锁的key
* @param timeoutMsecs 获取锁的超时时间
* @param expireMsecs 锁的有效期
*/
public RedisLock(RedisTemplate<String,Object> redisTemplate, String lockKey, int timeoutMsecs, int expireMsecs) {
this(redisTemplate, lockKey, timeoutMsecs);
this.expireMsecs = expireMsecs;
}
public String getLockKey() {
return lockKey;
}
/**
* 封装和jedis方法
* @param key
* @return
*/
private String get(final String key) {
Object obj = redisTemplate.opsForValue().get(key);
return obj != null ? obj.toString() : null;
}
/**
* 封装和jedis方法
* @param key
* @param value
* @return
*/
private boolean setNX(final String key, final String value) {
return redisTemplate.opsForValue().setIfAbsent(key,value);
}
/**
* 封装和jedis方法
* @param key
* @param value
* @return
*/
private String getSet(final String key, final String value) {
Object obj = redisTemplate.opsForValue().getAndSet(key,value);
return obj != null ? (String) obj : null;
}
/**
* 获取锁
* @return 获取锁成功返回ture,超时返回false
* @throws InterruptedException
*/
public boolean lock() throws InterruptedException {
// 线程获取锁的等待时间60s
int timeout = timeoutMsecs;
while (timeout >= 0) {
//锁的失效时间,到期时间(60s)
long expires = System.currentTimeMillis() + expireMsecs + 1;
String expiresStr = String.valueOf(expires);
//原子操作
if (this.setNX(lockKey, expiresStr)) {
locked = true;
return true;
}
//redis里key的值
String currentValue = this.get(lockKey);
//判断锁是否已经过期,过期则重新设置并获取
if (currentValue != null && Long.parseLong(currentValue) < System.currentTimeMillis()) {
//设置锁并返回旧值,redis里key的值
String oldValue = this.getSet(lockKey, expiresStr);
//比较锁的时间,如果不一致则可能是其他锁已经修改了值并获取
if (oldValue != null && oldValue.equals(currentValue)) {
locked = true;
return true;
}
}
timeout -= DEFAULT_ACQUIRY_RETRY_MILLIS;
//延时100毫秒
Thread.sleep(DEFAULT_ACQUIRY_RETRY_MILLIS);
}
return false;
}
/**
* 释放获取到的锁
*/
public void unlock() {
if (locked) {
redisTemplate.delete(lockKey);
locked = false;
}
}
@Override
public void close() throws IOException {
if (locked) {
redisTemplate.delete(lockKey);
locked = false;
}
}
}
4,测试分布式锁,写一个controller,启动项目,开启10个线程获取锁,进行测试
package com.daling.controller;
import com.daling.util.lock.redisTemplateLock.RedisLock;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.concurrent.atomic.AtomicInteger;
@Controller
@RequestMapping("/redis")
public class redisLockController {
private static AtomicInteger atomicInteger = new AtomicInteger(0);
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void checkJedisLock(String mobile) {
// 先获取锁,同一个手机号串行操作
// RedisLock实现了Closeable接口,try代码块结束后会执行close方法
try(RedisLock redisLock = new RedisLock(redisTemplate, mobile)){
boolean lock = redisLock.lock();
if (!lock) {
System.out.println("获取锁过于频繁");
//返回
return;
}else {
System.out.println("获取锁成功!!!!");
}
//停留3秒代表处理业务处理逻辑
Thread.sleep(3000);
System.out.println("atomicInteger = " + atomicInteger.incrementAndGet());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* http://localhost:8080/redis/testLock
*/
@RequestMapping("/testLock")
public void testLock() {
System.out.println("jlfjsljfs");
//启动10个线程模拟资源竞争
for (int i = 0; i < 10; i++) {
new Thread(() -> checkJedisLock("18336549989")).start();
}
}
}