1、需求:更新数据
*存在多个并发线程,尝试更新数据
*需要控制只能有一个线程更新数据
2、使用redis key 过期机制,实现同一时刻,只有一个线程执行
@Component
public class RedisManager {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/*
* 参数 KEYS[1] 为redis key
* 参数 KEYS[2] 为redis key 过期时间
* 参数 KEYS[3] 同一时刻运行,执行的线程数量
*
* 设置key 成功者返回1(true) ,否则返回0(false)
* */
private String ScriptStr =
" local times = redis.call('incr',KEYS[1]) " +
" if times == 1 then " +
" redis.call('expire',KEYS[1],KEYS[2]) " +
" end " +
" if times > tonumber(KEYS[3]) then " +
" return 0 " +
" end " +
" return 1";
private RedisScript<Boolean> Script = new DefaultRedisScript<>(ScriptStr, Boolean.class);
/**
* @param key redis key
* @param times 多少个线程同时访问
* @param expireTime key 过期时间
* @param timeUnit 过期时间 的时间单位
*/
public boolean limit(String key, Long times, Long expireTime, TimeUnit timeUnit) {
Long seconds = TimeoutUtils.toSeconds(expireTime, timeUnit);//过期时间
boolean isOK = redisTemplate.execute(Script, Lists.newArrayList(key, seconds.toString(), times.toString()));
return isOK;
}
}
@Component
public class MyTask {
@Autowired
private RedisManager redisManager;
@PostConstruct
public static void test() {
List<Thread> threads = new HashMap<>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(new Runnable() {
public void run() {
while (true) {
//设置key的值
if (redisManager.limit("uniqueKey", 1L, 200, TimeUnit.SECONDS)) {
System.out.println(new Date().getTime());
System.out.println(Thread.currentThread().getName());
//写数据
//写数据
}
try {
Thread.sleep(200 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}));
}
for (Thread thread : threads) {
thread.start();
}
}
注:
(1) 每个线程在写数据前,都尝试去设置"uniqueKey"的值,如果 uniqueKey不存在,则设置uniqueKey的值为1,返回true。
如果uniqueKey已存在,且uniqueKey的值大于1,返回false.
(2) key 的过期时间为200s,则线程写数据的操作必须在200s内完成,否则可能会查询多个线程同时写的情况。