线程锁:主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如synchronized是共享对象头,显示锁Lock是共享某个变量(state)
进程锁:为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过synchronized等线程锁实现进程锁。
分布式锁:当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。
即先用setnx来抢锁,如果抢到之后,再用expire给锁设置一个过期时间,防止锁忘记释放
// 获取锁 基于 setnx 和 expire 此方法不会保证原子性 可以使用lua脚本(redis 又演变出 set加过期时间的方式)
public boolean getLockNx(Jedis jedis, String lockeKey, String requestId, Long expireTime) {
Long setnx = jedis.setnx(lockeKey, requestId);
if (Objects.equals(setnx, 1)) {
jedis.expire(lockeKey, new Long(expireTime).intValue());
return true;
}
return false;
}
// 获取锁, 设置超时时间,单位为毫秒 此方法目前可以满足大多数需求
public boolean getLock(Jedis jedis, String lockKey, String requestId, Long expireTime) {
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
存在问题:
//释放锁 存在问题是会误删他人的锁
public boolean releaseLock(Jedis jedis1, String key, String requestId) {
try {
String result = jedis1.get(key);
if (Objects.equals(result, requestId)) {
// lockkey锁失效,下一步删除的就是别人的锁
jedis1.del(key);
return true;
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
jedis1.close();
}
return false;
}
/**
* 释放分布式锁 基于lua脚本释放 保证了原子性 和释放锁是符合自己的 解决并发问题
*
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
方案3还是可能存在「锁过期释放,业务没执行完」的问题。有些h会认为,稍微把锁过期时间设置长一些就可以啦。其实我们设想一下,是否可以给获得锁的线程,开启一个定时守护线程,每隔一段时间检查锁是否还存在,存在则对锁的过期时间延长,防止锁过期提前释放
开源框架Redisson解决了这个问题。我们一起来看下Redisson底层原理图吧:
只要线程一加锁成功,就会启动一个watch dog看门狗,它是一个后台线程,会每隔10秒检查一下,如果线程1还持有锁,那么就会不断的延长锁key的生存时间。因此,Redisson就是使用Redisson解决了「锁过期释放,业务没执行完」问题
//redlock 实现分布式锁 最终方案
public boolean getRedLock(RedissonClient redisClient){
RLock lock = redisClient.getLock("REDLOCK_KEY");
try {
boolean flag = lock.tryLock();
if (flag) {
System.out.println("加锁成功");
}
} catch (Exception ex){
} finally {
lock.unlock();
}
return false;
}