- 分布式锁
- 通过在redis设置一个特定值,只有成功设置这个值得线程,才被看做拿到了锁,并能去使用一些资源,使用结束后删除该值,使得其他线程能去拿到高锁。由于业务逻辑中可能出现异常,导致删除操作没有被执行,引发死锁,所以一般会给锁设置一个较短的有效值。
- 超时问题
- 由于业务逻辑有可能比较复杂,导致锁已经失效,但业务逻辑还没执行结束。所以一般不建议在较长时间的业务中使用redis分布式锁。
- 出现超时问题时,可能会出现由于锁自动释放,前一个线程执行解锁时实际解锁了下一个线程的锁,所以建议给每个线程设施一个特定随机值,再删除锁之前检查保存的值是否和自己预期的值一致,这里类似乐观锁的思想。
- 可重入
- 通过维护当前持有锁的计数来实现可重入功能。
- 代码如下:
package com.xliu.chapter1;
import redis.clients.jedis.Jedis;
import java.sql.Time;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class RedisWithReentrantLock {
private ThreadLocal<Map<String, Integer>> lockers = new ThreadLocal<>();
private ThreadLocal<String> value = new ThreadLocal<>();
private Jedis jedis;
public RedisWithReentrantLock(Jedis jedis) {
this.jedis = jedis;
}
private boolean _lock(String key,String value) {
return jedis.set(key, value, "nx", "ex", 60L) != null;
}
private void _unLock(String key) {
jedis.del(key);
}
private Map<String, Integer> currentLockers() {
Map<String, Integer> refs = lockers.get();
if (refs != null) {
return refs;
}
lockers.set(new HashMap<>());
return lockers.get();
}
private boolean lock(String key) {
Map<String, Integer> refs = currentLockers();
Integer refCnt = refs.get(key);
if (refCnt != null) {
refs.put(key, refCnt + 1);
return true;
}
String value = UUID.randomUUID().toString();
boolean ok = this._lock(key,value);
if(!ok){
return false;
}
refs.put(key,1);
this.value.set(value);
return true;
}
private boolean unlock(String key){
Map<String, Integer> refs = currentLockers();
Integer refCnt = refs.get(key);
if(refCnt == null){
return false;
}
String s = value.get();
if(s == null || !s.equals(jedis.get(key))){
return false;
}
refCnt -= 1;
if(refCnt > 0){
refs.put(key,refCnt-1);
}else{
refs.remove(key);
this._unLock(key);
}
return true;
}
public static void main(String[] args) throws InterruptedException {
Jedis jedis = new Jedis("192.168.198.128");
RedisWithReentrantLock redis = new RedisWithReentrantLock(jedis);
System.out.println("主线程第一次上锁:" + redis.lock("testLock"));
System.out.println("主线程第二次上锁:" + redis.lock("testLock"));
TimeUnit.SECONDS.sleep(1);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " try lock " + redis.lock("testLock"));
System.out.println(Thread.currentThread().getName() + " try unlock " +redis.unlock("testLock"));
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " try lock " + redis.lock("testLock"));
System.out.println(Thread.currentThread().getName() + " try unlock " +redis.unlock("testLock"));
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " try lock " + redis.lock("testLock"));
System.out.println(Thread.currentThread().getName() + " try unlock " +redis.unlock("testLock"));
}
});
thread.start();
TimeUnit.SECONDS.sleep(3);
System.out.println(redis.unlock("主线程第一次解锁:" + "testLock"));
TimeUnit.SECONDS.sleep(3);
System.out.println(redis.unlock("主线程第二次解锁:" + "testLock"));
}
}
- 运行结果:可以看到主线程首先进行了两次加锁操作,并成功拿到了锁,然后每3秒解锁一次。线程0尝试加锁和解锁均失败,在等待两次解锁完成后才拿到了锁。
主线程第一次上锁:true
主线程第二次上锁:true
Thread-0 try lock false
Thread-0 try unlock false
主线程第一次解锁:true
Thread-0 try lock false
Thread-0 try unlock false
主线程第二次解锁:true
Thread-0 try lock true
Thread-0 try unlock true