万字长文!不为人所知的分布式锁实现全都在这里了(1)

数据库乐观锁实现

乐观锁,以「乐观的心态」来操作共享资源,无法获得锁成功,没关系过一会重试一下看看呗,再不行就直接退出,尝试一定次数还是不行?也可以以后再说,不用一直阻塞等着。

1、有一张资源表

为表添加一个字段,版本号或者时间戳都可以。通过版本号或者时间戳,来保证多线程同时间操作共享资源的有序性和正确性。

CREATE TABLE resource (

id int(4) NOT NULL AUTO_INCREMENT COMMENT ‘主键’,

resource_name varchar(64) NOT NULL DEFAULT ‘’ COMMENT ‘资源名’,

share varchar(64) NOT NULL DEFAULT ‘’ COMMENT ‘状态’,

version int(4) NOT NULL DEFAULT ‘’ COMMENT ‘版本号’,

desc varchar(1024) NOT NULL DEFAULT ‘备注信息’,

update_time timestamp NOT NULL DEFAULT ‘’ COMMENT ‘保存数据时间,自动生成’,

PRIMARY KEY (id),

UNIQUE KEY uidx_resource_name (resource_name ) USING BTREE

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=‘资源’;

2、使用姿势

伪代码实现:

Resrouce resource = exeSql(“select * from resource where resource_name = xxx”);

boolean succ = exeSql(“update resource set version= ‘newVersion’ … where resource_name = xxx and version = ‘oldVersion’”);

if (!succ) {

// 发起重试

}

实际代码中可以写个while循环不断重试,版本号不一致,更新失败,重新获取新的版本号,直到更新成功。

3、乐观锁优缺点

优点:简单易用,保障数据一致性。

缺点:

1)加行锁的性能上有一定的开销

2)高并发场景下,线程内的自旋操作 会耗费一定的CPU资源。

另外,比如在更新数据状态的一些场景下,不考虑幂等性的情况下,可以直接利用 行锁 来保证数据一致性,示例:update table set state = 1 where id = xxx and state = 0;

乐观锁就类似 CAS Compare And Swap 更新机制,推荐阅读 <<一文彻底搞懂CAS>>


基于Redis分布式锁实现
基于SetNX实现分布式锁

基于Redis实现的分布式锁,性能上是最好的,实现上也是最复杂的。

前文中提到的 RedLock 是 Redis 之父 Antirez 提出来的分布式锁的一种 「健壮」 的实现算法,但争议也较多,一般不推荐使用。

Redis 2.6.12 之前的版本中采用 setnx + expire 方式实现分布式锁,示例代码如下所示:

public static boolean lock(Jedis jedis, String lockKey, String requestId, int expireTime) {

Long result = jedis.setnx(lockKey, requestId);

//设置锁

if (result == 1) {

//获取锁成功

//若在这里程序突然崩溃,则无法设置过期时间,将发生死锁

//通过过期时间删除锁

jedis.expire(lockKey, expireTime);

return true;

}

return false;

}

如果 lockKey 存在,则返回失败,否则返回成功。设置成功之后,为了能在完成同步代码之后成功释放锁,方法中使用 expire() 方法给 lockKey 设置一个过期时间,确认 key 值删除,避免出现锁无法释放,导致下一个线程无法获取到锁,即死锁问题。

但是 setnx + expire 两个命令放在程序里执行,不是原子操作,容易出事。

如果程序设置锁之后,此时,在设置过期时间之前,程序崩溃了,如果 lockKey 没有设置上过期时间,将会出现死锁问题

解决以上问题 ,有两个办法:

**1)方式一:**lua脚本

我们也可以通过 Lua 脚本来实现锁的设置和过期时间的原子性,再通过 jedis.eval() 方法运行该脚本:

// 加锁脚本,KEYS[1] 要加锁的key,ARGV[1]是UUID随机值,ARGV[2]是过期时间

private static final String SCRIPT_LOCK = “if redis.call(‘setnx’, KEYS[1], ARGV[1]) == 1 then redis.call(‘pexpire’, KEYS[1], ARGV[2]) return 1 else return 0 end”;

// 解锁脚本,KEYS[1]要解锁的key,ARGV[1]是UUID随机值

private static final String SCRIPT_UNLOCK = “if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘del’, KEYS[1]) else return 0 end”;

**2)方式二:**set原生命令

在 Redis 2.6.12 版本后 SETNX 增加了过期时间参数:

SET lockKey anystring NX PX max-lock-time

程序实现代码如下:

public static boolean lock(Jedis jedis, String lockKey, String requestId, int expireTime) {

String result = jedis.set(lockKey, requestId, “NX”, “PX”, expireTime);

if (“OK”.equals(result)) {

return true;

}

return false;

}

虽然 SETNX 方式能够保证设置锁和过期时间的原子性,但是如果我们设置的过期时间比较短,而执行业务时间比较长,就会存在锁代码块失效的问题,失效后其他客户端也能获取到同样的锁,执行同样的业务,此时可能就会出现一些问题。

我们需要将过期时间设置得足够长,来保证以上问题不会出现,但是设置多长时间合理,也需要依具体业务来权衡。如果其他客户端必须要阻塞拿到锁,需要设计循环超时等待机制等问题,感觉还挺麻烦的是吧。

Spring企业集成模式实现分布式锁

除了使用Jedis客户端之外,完全可以直接用Spring官方提供的企业集成模式框架,里面提供了很多分布式锁的方式,Spring提供了一个统一的分布式锁抽象,具体实现目前支持:

  • Gemfire

  • Jdbc

  • Zookeeper

  • Redis

早期,分布式锁的相关代码存在于Spring Cloud的子项目Spring Cloud Cluster中,后来被迁到Spring Integration中。

Spring Integration 项目地址 :https://github.com/spring-projects/spring-integration

Spring强大之处在于此,对Lock分布式锁做了全局抽象。

抽象结构如下所示:

17179731-ec293918459dea7c.jpg

LockRegistry 作为顶层抽象接口:

/**

  • Strategy for maintaining a registry of shared locks

  • @author Oleg Zhurakousky

  • @author Gary Russell

  • @since 2.1.1

*/

@FunctionalInterface

public interface LockRegistry {

/**

  • Obtains the lock associated with the parameter object.

  • @param lockKey The object with which the lock is associated.

  • @return The associated lock.

*/

Lock obtain(Object lockKey);

}

定义的 obtain() 方法获得具体的 Lock 实现类,分别在对应的 XxxLockRegitry 实现类来创建。

RedisLockRegistry 里obtain()方法实现类为 RedisLock,RedisLock内部,在Springboot2.x(Spring5)版本中是通过SET + PEXIPRE 命令结合lua脚本实现的,在Springboot1.x(Spring4)版本中,是通过SETNX命令实现的。

ZookeeperLockRegistry 里obtain()方法实现类为 ZkLock,ZkLock内部基于 Apache Curator 框架实现的。

JdbcLockRegistry 里obtain()方法实现类为 JdbcLock,JdbcLock内部基于一张INT_LOCK数据库锁表实现的,通过JdbcTemplate来操作。

客户端使用方法:

private final String registryKey = “sb2”;

RedisLockRegistry lockRegistry = new RedisLockRegistry(getConnectionFactory(), this.registryKey);

Lock lock = lockRegistry.obtain(“foo”);

lock.lock();

try {

// doSth…

}

finally {

lock.unlock();

}

}

下面以目前最新版本的实现,说明加锁和解锁的具体过程。

RedisLockRegistry$RedisLock类lock()加锁流程:

17179731-3fd65ba396440abe.jpg

加锁步骤:

1)lockKey为registryKey:path,本例中为sb2:foo,客户端C1优先申请加锁。

2)执行lua脚本,get lockKey不存在,则set lockKey成功,值为clientid(UUID),过期时间默认60秒。

3)客户端C1同一个线程重复加锁,pexpire lockKey,重置过期时间为60秒。

4)客户端C2申请加锁,执行lua脚本,get lockKey已存在,并且跟已加锁的clientid不同,加锁失败

5)客户端C2挂起,每隔100ms再次尝试加锁。

RedisLock#lock()加锁源码实现:

17179731-d755b546e9247dfe.jpg

大家可以对照上面的流程图配合你理解。

@Override

public void lock() {

this.localLock.lock();

while (true) {

try {

while (!obtainLock()) {

Thread.sleep(100); //NOSONAR

}

break;

}

catch (InterruptedException e) {

/*

  • This method must be uninterruptible so catch and ignore

  • interrupts and only break out of the while loop when

  • we get the lock.

*/

}

catch (Exception e) {

this.localLock.unlock();

rethrowAsLockException(e);

}

}

}

// 基于Spring封装的RedisTemplate来操作的

private boolean obtainLock() {

Boolean success =

RedisLockRegistry.this.redisTemplate.execute(RedisLockRegistry.this.obtainLockScript,

Collections.singletonList(this.lockKey), RedisLockRegistry.this.clientId,

String.valueOf(RedisLockRegistry.this.expireAfter));

boolean result = Boolean.TRUE.equals(success);

if (result) {

this.lockedAt = System.currentTimeMillis();

}

return result;

}

执行的lua脚本代码:

private static final String OBTAIN_LOCK_SCRIPT =

“local lockClientId = redis.call(‘GET’, KEYS[1])\n” +

“if lockClientId == ARGV[1] then\n” +

" redis.call(‘PEXPIRE’, KEYS[1], ARGV[2])\n" +

" return true\n" +

“elseif not lockClientId then\n” +

" redis.call(‘SET’, KEYS[1], ARGV[1], ‘PX’, ARGV[2])\n" +

" return true\n" +

“end\n” +

“return false”;

RedisLockRegistry$RedisLock类unlock()解锁流程:

17179731-9a2d7483a2ac5fe7.jpg

RedisLock#unlock()源码实现:

@Override

public void unlock() {

if (!this.localLock.isHeldByCurrentThread()) {

throw new IllegalStateException("You do not own lock at " + this.lockKey);

}

if (this.localLock.getHoldCount() > 1) {

this.localLock.unlock();

return;

}

try {

if (!isAcquiredInThisProcess()) {

throw new IllegalStateException("Lock was released in the store due to expiration. " +

“The integrity of data protected by this lock may have been compromised.”);

}

if (Thread.currentThread().isInterrupted()) {

RedisLockRegistry.this.executor.execute(this::removeLockKey);

}

else {

removeLockKey();

}

if (LOGGER.isDebugEnabled()) {

LOGGER.debug("Released lock; " + this);

}

}

catch (Exception e) {

ReflectionUtils.rethrowRuntimeException(e);

}

finally {

this.localLock.unlock();

}

}

// 删除缓存Key

private void removeLockKey() {

if (this.unlinkAvailable) {

try {

RedisLockRegistry.this.redisTemplate.unlink(this.lockKey);

}

catch (Exception ex) {

LOGGER.warn("The UNLINK command has failed (not supported on the Redis server?); " +

“falling back to the regular DELETE command”, ex);

this.unlinkAvailable = false;

RedisLockRegistry.this.redisTemplate.delete(this.lockKey);

}

}

else {

RedisLockRegistry.this.redisTemplate.delete(this.lockKey);

}

}

unlock()解锁方法里发现,并不是直接就调用Redis的DEL命令删除Key,这也是在Springboot2.x版本中做的一个优化,Redis4.0版本以上提供了UNLINK命令。

换句话说,最新版本分布式锁实现,要求是Redis4.0以上版本才能使用。

看下Redis官网给出的一段解释:

This command is very similar to DEL: it removes the specified keys.

Just like DEL a key is ignored if it does not exist. However the

command performs the actual memory reclaiming in a different thread,

so it is not blocking, while DEL is. This is where the command name

comes from: the command just unlinks the keys from the keyspace. The

actual removal will happen later asynchronously.

DEL始终在阻止模式下释放值部分。但如果该值太大,如对于大型LIST或HASH的分配太多,它会长时间阻止Redis,为了解决这个问题,Redis实现了UNLINK命令,即「非阻塞」删除。如果值很小,则DEL一般与UNLINK效率上差不多。

本质上,这种加锁方式还是使用的SETNX实现的,而且Spring只是做了一层薄薄的封装,支持可重入加锁,超时等待,可中断加锁。

但是有个问题,锁的过期时间不能灵活设置,客户端初始化时,创建RedisLockRegistry时允许设置,但是是全局的。

/**

  • Constructs a lock registry with the supplied lock expiration.

  • @param connectionFactory The connection factory.

  • @param registryKey The key prefix for locks.

  • @param expireAfter The expiration in milliseconds.

*/

public RedisLockRegistry(RedisConnectionFactory connectionFactory, String registryKey, long expireAfter) {

Assert.notNull(connectionFactory, “‘connectionFactory’ cannot be null”);

Assert.notNull(registryKey, “‘registryKey’ cannot be null”);

this.redisTemplate = new StringRedisTemplate(connectionFactory);

this.obtainLockScript = new DefaultRedisScript<>(OBTAIN_LOCK_SCRIPT, Boolean.class);

this.registryKey = registryKey;

this.expireAfter = expireAfter;

}

expireAfter参数是全局的,同样会存在问题,可能是锁过期时间到了,但是业务还没有处理完,这把锁又被另外的客户端获得,进而会导致一些其他问题。

经过对源码的分析,其实我们也可以借鉴RedisLockRegistry实现的基础上,自行封装实现分布式锁,比如:

1、允许支持按照不同的Key设置过期时间,而不是全局的?

2、当业务没有处理完成,当前客户端启动个定时任务探测,自动延长过期时间?

自己实现?嫌麻烦?别急别急!业界已经有现成的实现方案了,那就是 Redisson 框架!

站在Redis集群角度看问题

从Redis主从架构上来考虑,依然存在问题。因为 Redis 集群数据同步到各个节点时是异步的,如果在 Master 节点获取到锁后,在没有同步到其它节点时,Master 节点崩溃了,此时新的 Master 节点依然可以获取锁,所以多个应用服务可以同时获取到锁。

基于以上的考虑,Redis之父Antirez提出了一个RedLock算法

RedLock算法实现过程分析:

假设Redis部署模式是Redis Cluster,总共有5个master节点,通过以下步骤获取一把锁:

1)获取当前时间戳,单位是毫秒

2)轮流尝试在每个master节点上创建锁,过期时间设置较短,一般就几十毫秒

3)尝试在大多数节点上建立一个锁,比如5个节点就要求是3个节点(n / 2 +1)

4)客户端计算建立好锁的时间,如果建立锁的时间小于超时时间,就算建立成功了

5)要是锁建立失败了,那么就依次删除这个锁

6)只要有客户端创建成功了分布式锁,其他客户端就得不断轮询去尝试获取锁

以上过程前文也提到了,进一步分析RedLock算法的实现依然可能存在问题,也是Martain和Antirez两位大佬争论的焦点。

问题1:节点崩溃重启

节点崩溃重启,会出现多个客户端持有锁。

假设一共有5个Redis节点:A、B、 C、 D、 E。设想发生了如下的事件序列:

1)客户端C1成功对Redis集群中A、B、C三个节点加锁成功(但D和E没有锁住)。

2)节点C Duang的一下,崩溃重启了,但客户端C1在节点C加锁未持久化完,丢了。

3)节点C重启后,客户端C2成功对Redis集群中C、D、 E尝试加锁成功了。

这样,悲剧了吧!客户端C1和C2同时获得了同一把分布式锁。

为了应对节点重启引发的锁失效问题,Antirez提出了延迟重启的概念,即一个节点崩溃后,先不立即重启它,而是等待一段时间再重启,等待的时间大于锁的有效时间。

采用这种方式,这个节点在重启前所参与的锁都会过期,它在重启后就不会对现有的锁造成影响。

这其实也是通过人为补偿措施,降低不一致发生的概率。

问题2:时钟跳跃

假设一共有5个Redis节点:A、B、 C、 D、 E。设想发生了如下的事件序列:

1)客户端C1成功对Redis集群中A、B、 C三个节点成功加锁。但因网络问题,与D和E通信失败。

2)节点C上的时钟发生了向前跳跃,导致它上面维护的锁快速过期。

3)客户端C2对Redis集群中节点C、 D、 E成功加了同一把锁。

此时,又悲剧了吧!客户端C1和C2同时都持有着同一把分布式锁。

为了应对时钟跳跃引发的锁失效问题,Antirez提出了应该禁止人为修改系统时间,使用一个不会进行「跳跃式」调整系统时钟的ntpd程序。这也是通过人为补偿措施,降低不一致发生的概率。

但是…,RedLock算法并没有解决,操作共享资源超时,导致锁失效的问题。

存在这么大争议的算法实现,还是不推荐使用的。

一般情况下,本文锁介绍的框架提供的分布式锁实现已经能满足大部分需求了。

小结:

上述,我们对spring-integration-redis实现原理进行了深入分析,还对RedLock存在争议的问题做了分析。

除此以外,我们还提到了spring-integration中集成了 Jdbc、Zookeeper、Gemfire实现的分布式锁,Gemfire和Jdbc大家感兴趣可以自行去看下。

为啥还要提供个Jdbc分布式锁实现?

猜测一下,当你的应用并发量也不高,比如是个后台业务,而且还没依赖Zookeeper、Redis等额外的组件,只依赖了数据库。

但你还想用分布式锁搞点事儿,那好办,直接用spring-integration-jdbc即可,内部也是基于数据库行锁来实现的,需要你提前建好锁表,创建表的SQL长这样:

CREATE TABLE INT_LOCK (

LOCK_KEY CHAR(36) NOT NULL,

REGION VARCHAR(100) NOT NULL,

CLIENT_ID CHAR(36),

CREATED_DATE DATETIME(6) NOT NULL,

constraint INT_LOCK_PK primary key (LOCK_KEY, REGION)

) ENGINE=InnoDB;

具体实现逻辑也非常简单,大家自己去看吧。

集成的Zookeeper实现的分布式锁,因为是基于Curator框架实现的,不在本节展开,后续会有分析。

基于Redisson实现分布式锁

Redisson 是 Redis 的 Java 实现的客户端,其 API 提供了比较全面的 Redis 命令的支持。

Jedis 简单使用阻塞的 I/O 和 Redis 交互,Redission 通过 Netty 支持非阻塞 I/O。

Redisson 封装了锁的实现,让我们像操作我们的本地 Lock 一样去使用,除此之外还有对集合、对象、常用缓存框架等做了友好的封装,易于使用。

截止目前,Github上 Star 数量为 11.8k,说明该开源项目值得关注和使用。

Redisson分布式锁Github:

https://github.com/redisson/redisson/wiki/8.-Distributed-locks-and-synchronizers

Redisson 可以便捷的支持多种Redis部署架构:

  1. Redis 单机

  2. Master-Slave + Sentinel 哨兵

  3. Redis-Cluster集群

// Master-Slave配置

Config config = new Config();

MasterSlaveServersConfig serverConfig = config.useMasterSlaveServers()

.setMasterAddress(“”)

.addSlaveAddress(“”)

.setReadMode(ReadMode.SLAVE)

.setMasterConnectionPoolSize(maxActiveSize)

.setMasterConnectionMinimumIdleSize(maxIdleSize)

.setSlaveConnectionPoolSize(maxActiveSize)

.setSlaveConnectionMinimumIdleSize(maxIdleSize)

.setConnectTimeout(CONNECTION_TIMEOUT_MS) // 默认10秒

.setTimeout(socketTimeout)

;

RedissonClient redisson = Redisson.create(config);

RLock lock = redisson.getLock(“myLock”);

// 获得锁

lock.lock();

// 等待10秒未获得锁,自动释放

lock.lock(10, TimeUnit.SECONDS);

// 等待锁定时间不超过100秒

// 10秒后自动释放锁

boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);

if (res) {

try {

} finally {

lock.unlock();

}

}

使用上非常简单,RedissonClient客户端提供了众多的接口实现,支持可重入锁、、公平锁、读写锁、锁超时、RedLock等都提供了完整实现。

lock()加锁流程:

为了兼容老的版本,Redisson里都是通过lua脚本执行Redis命令的,同时保证了原子性操作。

加锁执行的lua脚本:

17179731-94d6fdec3c62cfeb.jpg

Redis里的Hash散列结构存储的。

参数解释:

KEY[1]:要加锁的Key名称,比如示例中的myLock。

ARGV[1]:针对加锁的Key设置的过期时间

ARGV[2]:Hash结构中Key名称,lockName为UUID:线程ID

protected String getLockName(long threadId) {

return id + “:” + threadId;

}

1)客户端C1申请加锁,key为myLock。

2)如果key不存在,通过hset设置值,通过pexpire设置过期时间。同时开启Watchdog任务,默认每隔10秒中判断一下,如果key还在,重置过期时间到30秒。

开启WatchDog源码:

17179731-0bfb163b97def419.jpg

17179731-bd458bc628afd3ae.jpg

3)客户端C1相同线程再次加锁,如果key存在,判断Redis里Hash中的lockName跟当前线程lockName相同,则将Hash中的lockName的值加1,代表支持可重入加锁。

4)客户单C2申请加锁,如果key存在,判断Redis里Hash中的lockName跟当前线程lockName不同,则执行pttl返回剩余过期时间。

5)客户端C2线程内不断尝试pttl时间,此处是基于Semaphore信号量实现的,有许可立即返回,否则等到pttl时间还是没有得到许可,继续重试。

重试源码:

17179731-929a7b51e3515239.jpg

Redisson这样的实现就解决了,当业务处理时间比过期时间长的问题。

同时,Redisson 还自己扩展 Lock 接口,叫做 RLock 接口,扩展了很多的锁接口,比如给 Key 设定过期时间,非阻塞+超时时间等。

void lock(long leaseTime, TimeUnit unit);

boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException;

redisson里的WatchDog(看门狗)逻辑保证了没有死锁发生。

如果客户端宕机了,WatchDog任务也就跟着停掉了。此时,不会对Key重置过期时间了,等挂掉的客户端持有的Key过期时间到了,锁自动释放,其他客户端尝试获得这把锁。

可以进一步看官网的关于WatchDog描述:

If Redisson instance which acquired lock crashes then such lock could hang forever in acquired state. To avoid this Redisson maintains lock watchdog, it prolongs lock expiration while lock holder Redisson instance is alive. By default lock watchdog timeout is 30 seconds and can be changed through Config.lockWatchdogTimeout setting.

unlock()解锁过程也是同样的,通过lua脚本执行一大坨指令的。

解锁lua脚本:

17179731-e6142f32c0d012f9.jpg

根据刚刚对加锁过程的分析,大家可以自行看下脚本分析下。

基于Zookeeper实现分布式锁

Zookeeper 是一种提供「分布式服务协调」的中心化服务,是以 Paxos 算法为基础实现的。Zookeeper数据节点和文件目录类似,同时具有Watch机制,基于这两个特性,得以实现分布式锁功能。

数据节点:

顺序临时节点:Zookeeper 提供一个多层级的节点命名空间(节点称为 Znode),每个节点都用一个以斜杠(/)分隔的路径来表示,而且每个节点都有父节点(根节点除外),非常类似于文件系统。

节点类型可以分为持久节点(PERSISTENT )、临时节点(EPHEMERAL),每个节点还能被标记为有序性(SEQUENTIAL),一旦节点被标记为有序性,那么整个节点就具有顺序自增的特点。

一般我们可以组合这几类节点来创建我们所需要的节点,例如,创建一个持久节点作为父节点,在父节点下面创建临时节点,并标记该临时节点为有序性。

Watch 机制:

Zookeeper 还提供了另外一个重要的特性,Watcher(事件监听器)。

ZooKeeper 允许用户在指定节点上注册一些 Watcher,并且在一些特定事件触发的时候,ZooKeeper 服务端会将事件通知给用户。

图解Zookeeper实现分布式锁:

17179731-bf8c49854c3fe649.jpg

首先,我们需要建立一个父节点,节点类型为持久节点(PERSISTENT)如图中的 /locks/lock_name1 节点 ,每当需要访问共享资源时,就会在父节点下建立相应的顺序子节点,节点类型为临时节点(EPHEMERAL),且标记为有序性(SEQUENTIAL),并且以临时节点名称 + 父节点名称 + 顺序号组成特定的名字,如图中的 /0000000001 /0000000002 /0000000003 作为临时有序节点。

在建立子节点后,对父节点下面的所有以临时节点名称 name 开头的子节点进行排序,判断刚刚建立的子节点顺序号是否是最小的节点,如果是最小节点,则获得锁。

如果不是最小节点,则阻塞等待锁,并且获得该节点的上一顺序节点,为其注册监听事件,等待节点对应的操作获得锁。当调用完共享资源后,删除该节点,关闭 zk,进而可以触发监听事件,释放该锁。

// 加锁

InterProcessMutex lock = new InterProcessMutex(client, lockPath);

if ( lock.acquire(maxWait, waitUnit) )

{

try

{

// do some work inside of the critical section here

}

finally

{

lock.release();

}

}

public void acquire() throws Exception

{

if ( !internalLock(-1, null) )

{

throw new IOException("Lost connection while trying to acquire lock: " + basePath);

}

}

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
QUENTIAL**),并且以临时节点名称 + 父节点名称 + 顺序号组成特定的名字,如图中的 /0000000001 /0000000002 /0000000003 作为临时有序节点。

在建立子节点后,对父节点下面的所有以临时节点名称 name 开头的子节点进行排序,判断刚刚建立的子节点顺序号是否是最小的节点,如果是最小节点,则获得锁。

如果不是最小节点,则阻塞等待锁,并且获得该节点的上一顺序节点,为其注册监听事件,等待节点对应的操作获得锁。当调用完共享资源后,删除该节点,关闭 zk,进而可以触发监听事件,释放该锁。

// 加锁

InterProcessMutex lock = new InterProcessMutex(client, lockPath);

if ( lock.acquire(maxWait, waitUnit) )

{

try

{

// do some work inside of the critical section here

}

finally

{

lock.release();

}

}

public void acquire() throws Exception

{

if ( !internalLock(-1, null) )

{

throw new IOException("Lost connection while trying to acquire lock: " + basePath);

}

}

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-Rd2qMHFX-1715191123000)]

[外链图片转存中…(img-kmodTLiK-1715191123000)]

[外链图片转存中…(img-aOSTZQpO-1715191123001)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值