Why
1、为什么要用锁?
为了保证一个方法或属性在高并发情况下的同一时间只能被同一个线程执行。锁是一种用来解决多个执行线程访问共享资源错误或数据不一致问题的工具。
2、为什么要用分布式锁?
分布式锁是针对分布式部署的应用设计的一种特殊的锁。单机部署的单体应用可以使用Java并发处理相关的API(如ReentrantLock或Synchronized)进行互斥控制。但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。
总结一句就是:Java API提供的锁实现是在同一个进程内的(相同的JVM下)共享资源管理,而分布式锁可以实现多个进程间的(跨JVM)共享资源管理。
What
分布式锁的几个特性
互斥性:一个方法在同一时间只能被一个进程内的一个线程执行,保证不同客户端的不同线程之间互斥。
可重入性:支持锁的重入,减少资源消耗。
锁超时释放:获取锁的客户端因为某些原因而宕机,而未能释放锁,其他客户端无法获取此锁。锁超时释放是为了避免死锁,解决锁被已终止线程一直占用问题。
安全性:锁只能被持有该锁的用户删除,而不能被其他用户删除。
高效与高可用:加锁与解锁需要高效,并保证高可用,当部分节点宕机,客户端仍能获取锁或者释放锁。
支持阻塞与非阻塞:阻塞就是线程获取不到锁一直阻塞,增加超时时间可以防止一直阻塞。非阻塞则获取不到锁不阻塞线程,直接返回获取锁失败。
支持公平与非公平:公平锁就是按照加锁的顺序获取到锁,非公平锁即无序。
How
分布式锁的几种实现方式
1、基于数据库表实现分布式锁
创建一张锁表,通过操作该表中的数据来实现。要锁住某个方法或资源时就在该表中增加一条记录,要释放锁时就删除这条记录。
数据库表:
CREATE TABLE `methodLock` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的方法名',
`desc` varchar(1024) NOT NULL DEFAULT '备注信息',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成',
PRIMARY KEY (`id`),
UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';
要锁住某个方法时,执行以下SQL:
insert into methodLock(method_name,desc) values (‘method_name’,‘desc’)
对method_name做了唯一性约束,这里如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么就可以认为操作成功的那个线程获得了该方法的锁,可以执行方法体内容。
当方法执行完毕之后,要释放锁的话,需要执行以下Sql:
delete from methodLock where method_name ='method_name'
方案分析:
互斥性:已实现。
可重入性:未实现,可通过记录请求来源信息实现(比如traceId、进程+线程信息等)。
锁超时释放:未实现,可设置定时任务,每隔一段时间把超时数据删除。
安全性:未实现,可增加身份认证信息,释放锁时增加身份认证筛选条件。
高效与高可用:依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。可通过设置从库的方式保证数据库的可用性,但数据库切换时效性和数据同步及时性较差。
支持阻塞与非阻塞:可通过应用程序控制,可实现。
支持公平与非公平:可通过应用程序控制,可实现。
2、基于缓存(Redis)实现分布式锁
获取锁的使用,使用setnx(当key不存在时set成功,否则set失败)加锁并设置过期时间,值为身份认证ID(可以使用时间戳或UUID)解锁时使用。
获取到锁的线程执行方法体内容,未获取到锁的线程继续准备获取锁。由于没有锁释放通知其它线程重新获取锁的实现,因为在循环获取锁时需要配合一定的sleep操作。
方法体执行完毕后,释放锁。先根据value的值来判断是不是自己的锁,如果是的话则删除,不是则表明自己的锁已经过期,不需要删除。(此时出现由于过期而导致的多进程同时拥有锁的问题,建议配置响应的告警处理,expire时间有问题。)判断时的获取value和删除操作可通过执行编写的Lua代码实现原子方式的执行效果。
Code
依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
LockManager接口
public interface LockManager {
/**
* 获取锁(非阻塞锁)
* @param key 锁key
* @return 获取锁标识,用于释放锁的身份验证,返回NULL则获取锁失败。
*/
String tryNonBlockLock(String key);
/**
* 获取锁(非阻塞锁)
* @param key 锁key
* @param expire 占用时间(ms)
* @return 获取锁标识,用于释放锁的身份验证,返回NULL则获取锁失败。
*/
String tryNonBlockLock(String key, int expire);
/**
* 获取锁(阻塞锁)
* @param key 锁key
* @return 获取锁标识,用于释放锁的身份验证,返回NULL则获取锁失败。
*/
String tryLock(String key);
/**
* 获取锁(阻塞锁)
* @param key 锁key
* @param expire 占用时间(ms)
* @return 获取锁标识,用于释放锁的身份验证,返回NULL则获取锁失败。
*/
String tryLock(String key, int expire);
/**
* 获取锁(阻塞锁)
* @param key 锁key
* @param expire 占用时间(ms)
* @param timeout 获取锁超时时间(ms)
* @return 获取锁标识,用于释放锁的身份验证,返回NULL则获取锁失败。
*/
String tryLock(String key, int expire, int timeout);
/**
* 释放锁
* @param key 锁key
* @param authId 锁标识Id
* @return 释放是否成功
*/
boolean unLock(String key, String authId);
}
LockManager接口实现类
public class LockManagerImpl implements LockManager {
/**
* 默认超时时间(ms)
*/
private static final int DEFAULT_TIMEOUT = 60000;
/**
* 解锁的lua脚本
*/
private static final String UNLOCK_LUA;
static {
StringBuilder sb = new StringBuilder();
sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
sb.append("then ");
sb.append(" return redis.call(\"del\",KEYS[1]) ");
sb.append("else ");
sb.append(" return 0 ");
sb.append("end ");
UNLOCK_LUA = sb.toString();
}
@Override
public String tryNonBlockLock(String key) {
return tryNonBlockLock(key, -1);
}
@Override
public String tryNonBlockLock(String key, int expire) {
String res = null;
JedisPool pool = RedisUtil.getJedisPool();
// try with resource 形式获取资源,自动关闭Jedis
try (Jedis jedis = pool.getResource()){
String authId = UUID.randomUUID().toString();
String setRes = expire <= 0 ? jedis.set(key, authId, "NX") : jedis.set(key, authId, "NX","PX", expire);
if ("OK".equals(setRes)) {
res = authId;
}
}
return res;
}
@Override
public String tryLock(String key) {
return tryLock(key, -1, DEFAULT_TIMEOUT);
}
@Override
public String tryLock(String key, int expire) {
return tryLock(key, expire, DEFAULT_TIMEOUT);
}
public String tryLock(String key, int expire, int timeout) {
String res = null;
JedisPool pool = RedisUtil.getJedisPool();
// try with resource 形式获取资源,自动关闭Jedis
try (Jedis jedis = pool.getResource()){
long beginTime = System.currentTimeMillis();
// 自旋重试
while (System.currentTimeMillis() - beginTime < timeout) {
String authId = UUID.randomUUID().toString();
String setRes = expire <= 0 ? jedis.set(key, authId, "NX") : jedis.set(key, authId, "NX","PX", expire);
if ("OK".equals(setRes)) {
res = authId;
break;
}
// 随机sleep(0-1000ms),待优化建议根据timeout设置sleep,不建议设置为固定时间,否则获取锁的顺序将被固定。
Thread.sleep((long) (1000 * Math.random()));
}
} catch (Exception e) {
System.err.println(e.getMessage());
}
return res;
}
public boolean unLock(String key, String authId) {
if (null == key || null == authId) {
return false;
}
Long result = 0L;
JedisPool pool = RedisUtil.getJedisPool();
try (Jedis jedis = pool.getResource()) {
result = (Long) jedis.eval(UNLOCK_LUA, Arrays.asList(key), Arrays.asList(authId));
} catch (Exception e) {
System.err.println(e.getMessage());
}
return result == 1;
}
}
RedisUtil提供获取线程池方法
public class RedisUtil {
/**
* JedisPool线程池
*/
private static JedisPool pool;
/**
* JedisPool线程池配置
*/
static {
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
config.setMaxTotal(20);
config.setMaxIdle(10);
config.setMinIdle(5);
pool = new JedisPool(config, "127.0.0.1", 6379);
}
public static JedisPool getJedisPool() {
return pool;
}
}
测试类
public class Main {
public static void main(String[] args) throws InterruptedException{
// blockLuckTest();
nonBlockLuckTest();
}
private static void blockLuckTest() {
final LockManager lockManager = new LockManagerImpl();
for (int i = 0; i < 10; i ++) {
Thread thread = new Thread(() -> {
String val = lockManager.tryLock("lock1-key", 2000, 10000);
System.out.println("lock:" + Thread.currentThread().getName() + ":" +
(val == null ? "获取锁失败" : "获取锁成功"));
if (val != null) {
try {
Thread.sleep(1500L);
} catch (Exception e) {
System.err.println(e.getMessage());
}
boolean unlock = lockManager.unLock("lock1-key", val);
System.out.println("unlock:" + Thread.currentThread().getName() + ":" + (unlock ? "解锁成功" : "解锁失败"));
}
});
thread.setName("Process-1-Thread--" + i);
thread.start();
}
}
private static void nonBlockLuckTest() throws InterruptedException{
final LockManager lockManager = new LockManagerImpl();
for (int i = 0; i < 10; i ++) {
Thread thread = new Thread(() -> {
String val = lockManager.tryNonBlockLock("lock1-key", 2000);
System.out.println("lock:" + Thread.currentThread().getName() + ":" +
(val == null ? "获取锁失败" : "获取锁成功"));
if (val != null) {
try {
Thread.sleep(1500L);
} catch (Exception e) {
System.err.println(e.getMessage());
}
boolean unlock = lockManager.unLock("lock1-key", val);
System.out.println("unlock:" + Thread.currentThread().getName() + ":" + (unlock ? "解锁成功" : "解锁失败"));
}
});
thread.setName("Process-1-Thread--" + i);
thread.start();
Thread.sleep(1000L);
}
}
}
测试结果
blockLuckTest(); // 测试最后三个线程等待超时获取锁失败。
lock:Process-1-Thread--1:获取锁成功
unlock:Process-1-Thread--1:解锁成功
lock:Process-1-Thread--6:获取锁成功
unlock:Process-1-Thread--6:解锁成功
lock:Process-1-Thread--4:获取锁成功
unlock:Process-1-Thread--4:解锁成功
lock:Process-1-Thread--5:获取锁成功
unlock:Process-1-Thread--5:解锁成功
lock:Process-1-Thread--3:获取锁成功
unlock:Process-1-Thread--3:解锁成功
lock:Process-1-Thread--7:获取锁成功
unlock:Process-1-Thread--7:解锁成功
lock:Process-1-Thread--9:获取锁成功
lock:Process-1-Thread--8:获取锁失败
lock:Process-1-Thread--2:获取锁失败
lock:Process-1-Thread--0:获取锁失败
unlock:Process-1-Thread--9:解锁成功
nonBlockLuckTest();非阻塞锁,每隔1s启动一个,每隔锁占用1.5s,测试结果为每隔一个线程获取锁成功。
lock:Process-1-Thread--0:获取锁成功
lock:Process-1-Thread--1:获取锁失败
unlock:Process-1-Thread--0:解锁成功
lock:Process-1-Thread--2:获取锁成功
lock:Process-1-Thread--3:获取锁失败
unlock:Process-1-Thread--2:解锁成功
lock:Process-1-Thread--4:获取锁成功
lock:Process-1-Thread--5:获取锁失败
unlock:Process-1-Thread--4:解锁成功
lock:Process-1-Thread--6:获取锁成功
lock:Process-1-Thread--7:获取锁失败
unlock:Process-1-Thread--6:解锁成功
lock:Process-1-Thread--8:获取锁成功
lock:Process-1-Thread--9:获取锁失败
unlock:Process-1-Thread--8:解锁成功
方案分析:
互斥性:已实现。
可重入性:未实现,可通过记录请求来源信息实现(比如traceId、进程+线程信息等)。
锁超时释放:已实现,关于如何设置expire时间的讨论有待商榷。
安全性:已实现。
高效与高可用:依赖Redis的高可用。
支持阻塞与非阻塞:已实现。
支持公平与非公平:以上实现为非公平锁,可通过队列的方式实现公平锁
3、基于Zookeeper实现分布式锁
基于zookeeper临时顺序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的临时顺序节点。
获取锁只需判断自己创建的节点是否为有序节点中序号最小的一个,如果是则获取锁成功;否则监听前一个序号节点,抢锁失败进入等待状态。
释放锁只需删除自己创建的节点删除即可。监控节点收到删除通知后,主动获取锁。任务完成和任务执行过程中客户端崩溃都会释放锁。
Code
依赖
<dependencies>
<!-- zookeeper包 -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
</dependencies>
DistributedLock接口
public interface DistributedLock {
/**
* 获取锁
* @return 获取锁结果
*/
boolean tryLock();
/**
* 获取锁(非阻塞)
* @return 获取锁结果
*/
boolean tryNonBlockLock();
/**
* 解锁
*/
void unlock();
/**
* 释放zk资源
*/
void closeResource();
}
DistributedLock接口实现类DistributedLockImpl
public class DistributedLockImpl implements Watcher, DistributedLock {
/**
* ZooKeeper连接
*/
private ZooKeeper zk = null;
/**
* 根节点
*/
private static final String ROOT_LOCK = "/locks";
/**
* lockName与序号的分隔符
*/
private static final String splitStr = "_lock_";
/**
* 锁资源名称
*/
private String lockName;
/**
* 等待的前一个锁名称(lockName+no)
*/
private String waitLock;
/**
* 当前锁名称(lockName+no)
*/
private String curLock;
/**
* 计数器
*/
private CountDownLatch countDownLatch;
/**
* ZooKeeper session timeout
*/
private int sessionTimeout = 30000;
/**
* 配置分布式锁
* @param config 连接的url
* @param lockName 竞争资源
*/
public DistributedLockImpl(String config, String lockName) throws Exception{
if (lockName.contains(splitStr)) {
throw new Exception("锁名有误");
}
this.lockName = lockName;
try {
// 连接zookeeper
zk = new ZooKeeper(config, sessionTimeout, this);
if (zk.exists(ROOT_LOCK, false) == null) {
// 如果根节点不存在,则创建根节点
zk.create(ROOT_LOCK, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
/**
* Watcher调用用方法,前置节点被删除时执行。
* @param watchedEvent watch事件
*/
public void process(WatchedEvent watchedEvent) {
if (this.countDownLatch != null) {
this.countDownLatch.countDown();
}
}
/**
* 获取锁(阻塞)
* @return 获取锁结果
*/
public boolean tryLock() {
try {
if (this.tryLockHelper()) {
return true;
}
return waitForLock();
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 获取锁(非阻塞)
* @return 获取锁结果
*/
public boolean tryNonBlockLock() {
try {
Stat stat = zk.exists(ROOT_LOCK + "/" + lockName, false);
if (stat == null) {
String curLockFullPath = zk.create(ROOT_LOCK + "/" + lockName, new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
curLock = curLockFullPath.substring(curLockFullPath.lastIndexOf("/") + 1);
return true;
} else {
return false;
}
} catch (InterruptedException ex) {
ex.printStackTrace();
} catch (KeeperException ex) {
ex.printStackTrace();
}
return false;
}
/**
* 创建节点,设置属性(当前锁节点、等待前置节点)
* 判断节点序号是否为最小值,如果是返回true,否则返回false。
* @return 节点序号是否为最小值
*/
private boolean tryLockHelper() {
try {
// 创建临时有序节点
String curLockFullPath = zk.create(ROOT_LOCK + "/" + lockName + splitStr, new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
curLock = curLockFullPath.substring(curLockFullPath.lastIndexOf("/") + 1);
// 获取跟节点的所有子节点
List<String> nodeList = zk.getChildren(ROOT_LOCK, false);
List<String> sameLockNameList = new ArrayList<>();
for (String node : nodeList) {
String nodeLockName = node.split(splitStr)[0];
if (lockName.equals(nodeLockName)) {
sameLockNameList.add(node);
}
}
Collections.sort(sameLockNameList);
if (curLock.equals(sameLockNameList.get(0))) {
// 获取锁成功,设置返回值
return true;
} else {
// 获取锁失败,寻找并设置等待锁对象
int curLockIndex = Collections.binarySearch(sameLockNameList, curLock);
waitLock = sameLockNameList.get(curLockIndex - 1);
}
} catch (KeeperException ex) {
ex.printStackTrace();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
return false;
}
/**
* 等待锁
*/
private boolean waitForLock() throws KeeperException, InterruptedException {
// 检测等待的前置节点是否存在,并注册watcher对象,等待的前置节点被删除时调用watcher对方的process方法(countDownLatch减一)。
Stat stat = zk.exists(ROOT_LOCK + "/" + waitLock, true);
System.out.println(Thread.currentThread().getName() + "(" + ROOT_LOCK + "/" + curLock + ")等待锁 " + ROOT_LOCK + "/" + waitLock);
// 如果前置节点存在则等待前置节点删除回掉执行获取锁成功操作,否则直接返回获取锁成功(此时前置节点消失,可能因为连接中断导致)
if (stat != null) {
this.countDownLatch = new CountDownLatch(1);
// 计数等待,若等到前一个节点消失,则precess中进行countDown,停止等待,获取锁
this.countDownLatch.await();
this.countDownLatch = null;
}
return true;
}
/**
* 解锁
*/
public void unlock() {
try {
zk.delete(ROOT_LOCK + "/" + curLock, -1);
curLock = null;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
/**
* 释放zk资源
*/
public void closeResource() {
try {
zk.close();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
测试类
public class Main {
public static void main(String[] args) throws Exception {
blockLuckTest();
// nonBlockLuckTest();
}
private static void blockLuckTest() {
for (int i = 0; i < 10; i ++) {
Thread thread = new Thread(() -> {
try {
DistributedLock lock = new DistributedLockImpl("127.0.0.1:2181", "lock1-key");
boolean tryLockRes = lock.tryLock();
System.out.println(Thread.currentThread().getName() + "获取锁结果:" + tryLockRes);
if (tryLockRes) {
Thread.sleep(1500);
System.out.println(Thread.currentThread().getName() + "释放锁");
lock.unlock();
}
lock.closeResource();
} catch (Exception ex) {
ex.printStackTrace();
}
});
thread.setName("Process-1-Thread--" + i);
thread.start();
}
}
private static void nonBlockLuckTest() throws InterruptedException{
for (int i = 0; i < 10; i ++) {
Thread thread = new Thread(() -> {
try {
DistributedLock lock = new DistributedLockImpl("127.0.0.1:2181", "lock1-key");
boolean tryLockRes = lock.tryNonBlockLock();
System.out.println(Thread.currentThread().getName() + "获取锁结果:" + tryLockRes);
if (tryLockRes) {
try {
Thread.sleep(1500L);
} catch (Exception e) {
System.err.println(e.getMessage());
}
System.out.println(Thread.currentThread().getName() + "释放锁");
lock.unlock();
}
lock.closeResource();
} catch (Exception ex) {
ex.printStackTrace();
}
});
thread.setName("Process-1-Thread--" + i);
thread.start();
Thread.sleep(1000L);
}
}
}
测试结果
blockLuckTest(); //依次获取锁成功
Process-1-Thread--2获取锁结果:true
Process-1-Thread--4(/locks/lock1-key_lock_0000000076)等待锁 /locks/lock1-key_lock_0000000075
Process-1-Thread--1(/locks/lock1-key_lock_0000000072)等待锁 /locks/lock1-key_lock_0000000071
Process-1-Thread--0(/locks/lock1-key_lock_0000000078)等待锁 /locks/lock1-key_lock_0000000077
Process-1-Thread--8(/locks/lock1-key_lock_0000000073)等待锁 /locks/lock1-key_lock_0000000072
Process-1-Thread--3(/locks/lock1-key_lock_0000000075)等待锁 /locks/lock1-key_lock_0000000074
Process-1-Thread--6(/locks/lock1-key_lock_0000000071)等待锁 /locks/lock1-key_lock_0000000070
Process-1-Thread--5(/locks/lock1-key_lock_0000000077)等待锁 /locks/lock1-key_lock_0000000076
Process-1-Thread--7(/locks/lock1-key_lock_0000000079)等待锁 /locks/lock1-key_lock_0000000078
Process-1-Thread--9(/locks/lock1-key_lock_0000000074)等待锁 /locks/lock1-key_lock_0000000073
Process-1-Thread--2释放锁
Process-1-Thread--6获取锁结果:true
Process-1-Thread--6释放锁
Process-1-Thread--1获取锁结果:true
Process-1-Thread--1释放锁
Process-1-Thread--8获取锁结果:true
Process-1-Thread--8释放锁
Process-1-Thread--9获取锁结果:true
Process-1-Thread--9释放锁
Process-1-Thread--3获取锁结果:true
Process-1-Thread--3释放锁
Process-1-Thread--4获取锁结果:true
Process-1-Thread--4释放锁
Process-1-Thread--5获取锁结果:true
Process-1-Thread--5释放锁
Process-1-Thread--0获取锁结果:true
Process-1-Thread--0释放锁
Process-1-Thread--7获取锁结果:true
Process-1-Thread--7释放锁
nonBlockLuckTest(); //非阻塞锁
Process-1-Thread--2获取锁结果:true
org.apache.zookeeper.KeeperException$NodeExistsException: KeeperErrorCode = NodeExists for /locks/test1
at org.apache.zookeeper.KeeperException.create(KeeperException.java:119)
at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
at org.apache.zookeeper.ZooKeeper.create(ZooKeeper.java:783)
at zookeeper.impl.DistributedLockImpl.tryNonBlockLock(DistributedLockImpl.java:114)
at zookeeper.Main.lambda$nonBlockLuckTest$1(Main.java:38)
at java.lang.Thread.run(Thread.java:748)
org.apache.zookeeper.KeeperException$NodeExistsException: KeeperErrorCode = NodeExists for /locks/test1Process-1-Thread--4获取锁结果:false
at org.apache.zookeeper.KeeperException.create(KeeperException.java:119)
at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
at org.apache.zookeeper.ZooKeeper.create(ZooKeeper.java:783)
at zookeeper.impl.DistributedLockImpl.tryNonBlockLock(DistributedLockImpl.java:114)
at zookeeper.Main.lambda$nonBlockLuckTest$1(Main.java:38)
at java.lang.Thread.run(Thread.java:748)
Process-1-Thread--3获取锁结果:false
org.apache.zookeeper.KeeperException$NodeExistsException: KeeperErrorCode = NodeExists for /locks/test1
at org.apache.zookeeper.KeeperException.create(KeeperException.java:119)
at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
at org.apache.zookeeper.ZooKeeper.create(ZooKeeper.java:783)
at zookeeper.impl.DistributedLockImpl.tryNonBlockLock(DistributedLockImpl.java:114)
at zookeeper.Main.lambda$nonBlockLuckTest$1(Main.java:38)
at java.lang.Thread.run(Thread.java:748)
Process-1-Thread--1获取锁结果:false
org.apache.zookeeper.KeeperException$NodeExistsException: KeeperErrorCode = NodeExists for /locks/test1
at org.apache.zookeeper.KeeperException.create(KeeperException.java:119)
at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
at org.apache.zookeeper.ZooKeeper.create(ZooKeeper.java:783)
at zookeeper.impl.DistributedLockImpl.tryNonBlockLock(DistributedLockImpl.java:114)
at zookeeper.Main.lambda$nonBlockLuckTest$1(Main.java:38)
at java.lang.Thread.run(Thread.java:748)
org.apache.zookeeper.KeeperException$NodeExistsException: KeeperErrorCode = NodeExists for /locks/test1
Process-1-Thread--5获取锁结果:false
at org.apache.zookeeper.KeeperException.create(KeeperException.java:119)
at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
at org.apache.zookeeper.ZooKeeper.create(ZooKeeper.java:783)
at zookeeper.impl.DistributedLockImpl.tryNonBlockLock(DistributedLockImpl.java:114)
at zookeeper.Main.lambda$nonBlockLuckTest$1(Main.java:38)
at java.lang.Thread.run(Thread.java:748)
Process-1-Thread--0获取锁结果:false
Process-1-Thread--6获取锁结果:false
Process-1-Thread--2释放锁
Process-1-Thread--7获取锁结果:true
Process-1-Thread--8获取锁结果:false
Process-1-Thread--7释放锁
Process-1-Thread--9获取锁结果:true
Process-1-Thread--9释放锁
方案分析:
互斥性:已实现。
可重入性:未实现,可通过记录请求来源信息实现(比如traceId、进程+线程信息等)。
锁超时释放:未实现,但节点为临时顺序节点,客户端连接中断后,节点自动删除,因此也不存在设置expire长短问题。
安全性:已实现,区别于Redis分布式锁,每个节点唯一,因此,不存在不同线程间解锁错乱问题。
高效与高可用:依赖ZooKeeper的高可用。
支持阻塞与非阻塞:已实现,超时阻塞待实现。
支持公平与非公平:以上实现为公平锁,非公平锁待实现。