同步锁
多线程访问共享资源的时候,为了防止发生资源争抢,持有资源不能释放等一系列问题,就需要锁来防止并发访问共享资源。因为是单进程在访问,所以线程锁(同步锁等)就能完成。
分布式锁
在开始访问共享资源的程序部署在同一台服务器上,所以使用程序中的同步锁就可以实现共享资源的顺序访问,随着微服务架构的流行,越来越多的服务部署在不止一台服务器上,就出现了分布式、多进程访问共享资源的问题,就需要分布式锁解决。
实现方式
分布式锁的实现方式,常用的有三种:
- 数据库乐观锁实现
- Zookeeper实现
- 缓存实现
三种方案的比较
上面几种方式,哪种方式都无法做到完美。就像CAP一样,在复杂性、可靠性、性能等方面无法同时满足,所以,根据不同的应用场景选择最适合自己的才是王道。
从理解的难易程度角度(从低到高)
数据库 > 缓存 > Zookeeper
从实现的复杂性角度(从低到高)
Zookeeper >= 缓存 > 数据库
从性能角度(从高到低)
缓存 > Zookeeper >= 数据库
从可靠性角度(从高到低)
Zookeeper > 缓存 > 数据库
redis实现
RedisTool 分布式锁工具类
/**
* 参考网址:https://www.cnblogs.com/linjiqin/p/8003838.html
*/
public class RedisTool {
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
private static final Long RELEASE_SUCCESS = 1L;
/**
* 尝试获取分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int 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;
}
/**
* 释放分布式锁
* @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 (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
模拟并发访问的线程类
public class DistributeLockClientThread implements Runnable {
private String requestId;
private Jedis jedis;
public DistributeLockClientThread(String requestId, Jedis jedis) {
this.requestId = requestId;
this.jedis = jedis;
}
public void run() {
boolean flag = RedisTool.tryGetDistributedLock(jedis,"distributelock",requestId,5000);
if(flag){
System.out.println(requestId +"==get lock success");
}else {
System.out.println(requestId +"==get lock fail");
}
try {
Random random = new Random();
Thread.sleep(1000* random.nextInt(4));
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean releaseFlag = RedisTool.releaseDistributedLock(jedis,"distributelock",requestId);
if(releaseFlag){
System.out.println(requestId +"==release lock success");
}else {
System.out.println(requestId +"==release lock fail");
}
jedis.close();
}
}
测试类
public class DistributeLockTest {
public static void main(String[] args) {
JedisPoolConfig config = new JedisPoolConfig();
// 设置最大连接数
config.setMaxIdle(100);
// 设置最大阻塞时间,记住是毫秒数milliseconds
config.setMaxWaitMillis(10000);
// 设置空间连接
config.setMaxIdle(100);
// 创建连接池
JedisPool pool = new JedisPool(config, "192.168.222.188",6379);
ExecutorService executorService = Executors.newFixedThreadPool(1000);
for (int i=0;i<1000;i++){
DistributeLockClientThread distributeLockClientThread = new DistributeLockClientThread(String.valueOf(i),pool.getResource());
executorService.execute(distributeLockClientThread);
}
executorService.shutdown();
}
}