Java分布式锁

boolean lock = RedisLockUtil.lock2(key, 60);//默认60失效

if (lock) {

try {

//执行领红包操作

System.out.println(“红包领取成功”);

} finally {

//释放锁

redisLockUtil.unLock2(key);

}

} else {

new RuntimeException(“重复领取奖励”);

}

}

7.基于 REDLOCK 做分布式锁

===============================================================================

多节点redis实现的分布式锁算法(RedLock):有效防止单点故障

Redlock 是 Redis 的作者 antirez 给出的集群模式的 Redis 分布式锁,它基于 N 个完全独立的 Redis 节点(通常情况下 N 可以设置成 5)。

算法的步骤如下:

假设有5个完全独立的redis主服务器

  • 1.获取当前时间戳

  • 2.client尝试按照顺序使用相同的key,value获取所有redis服务的锁,在获取锁的过程中的获取时间比锁过期时间短很多,这是为了不要过长时间等待已经关闭的redis服务。并且试着获取下一个redis实例。

比如:TTL(Time To Live 中文为:生存时间值,该字段指定IP包被路由器丢弃之前允许通过的最大网段数量,简单理解就是过期时间)为5s,设置获取锁最多用1s,所以如果一秒内无法获取锁,就放弃获取这个锁,从而尝试获取下个锁

  • 3.client通过获取所有能获取的锁后的时间减去第一步的时间,这个时间差要小于TTL时间并且至少有3个redis实例成功获取锁,才算真正的获取锁成功

  • 4.如果成功获取锁,则锁的真正有效时间是 TTL减去第三步的时间差 的时间;比如:TTL 是5s,获取所有锁用了2s,则真正锁有效时间为3s(其实应该再减去时钟漂移);

  • 5.如果客户端由于某些原因获取锁失败,便会开始解锁所有redis实例;因为可能已经获取了小于3个锁,必须释放,否则影响其他client获取锁

算法示意图如下:

在这里插入图片描述

使用 Redlock 算法,可以保证在挂掉最多 2 个节点的时候,分布式锁服务仍然能工作,这相比之前的数据库锁和缓存锁大大提高了可用性,由于 redis 的高效性能,分布式缓存锁性能并不比数据库锁差。

7.1 RedLock算法是否是异步算法?


可以看成是同步算法;因为 即使进程间(多个电脑间)没有同步时钟,但是每个进程时间流速大致相同;并且时钟漂移相对于TTL叫小,可以忽略,所以可以看成同步算法;(不够严谨,算法上要算上时钟漂移,因为如果两个电脑在地球两端,则时钟漂移非常大)

7.2 RedLock失败重试


当client不能获取锁时,应该在随机时间后重试获取锁;并且最好在同一时刻并发的把set命令发送给所有redis实例;而且对于已经获取锁的client在完成任务后要及时释放锁,这是为了节省时间;

7.3 RedLock释放锁


由于释放锁时会判断这个锁的value是不是自己设置的,如果是才删除;所以在释放锁时非常简单,只要向所有实例都发出释放锁的命令,不用考虑能否成功释放锁;

7.4 RedLock注意点


1.先假设client获取所有实例,所有实例包含相同的key和过期时间(TTL) ,但每个实例set命令时间不同导致不能同时过期,第一个set命令之前是T1,最后一个set命令后为T2,则此client有效获取锁的最小时间为TTL-(T2-T1)-时钟漂移;

2.对于以N/2+ 1(也就是一半以 上)的方式判断获取锁成功,是因为如果小于一半判断为成功的话,有可能出现多个client都成功获取锁的情况, 从而使锁失效

3.一个client锁定大多数事例耗费的时间大于或接近锁的过期时间,就认为锁无效,并且解锁这个redis实例(不执行业务) ;只要在TTL时间内成功获取一半以上的锁便是有效锁;否则无效

7.5 RedLock性能及崩溃恢复的相关解决方法


1.如果redis没有持久化功能,在clientA获取锁成功后,所有redis重启,clientB能够再次获取到锁,这样违法了锁的排他互斥性;

2.如果启动AOF永久化存储,事情会好些, 举例:当我们重启redis后,由于redis过期机制是按照unix时间戳走的,所以在重启后,然后会按照规定的时间过期,不影响业务;但是由于AOF同步到磁盘的方式默认是每秒-次,如果在一秒内断电,会导致数据丢失,立即重启会造成锁互斥性失效;但如果同步磁盘方式使用Always(每一个写命令都同步到硬盘)造成性能急剧下降;所以在锁完全有效性和性能方面要有所取舍;

3.有效解决既保证锁完全有效性及性能高效及即使断电情况的方法是redis同步到磁盘方式保持默认的每秒,在redis无论因为什么原因停掉后要等待TTL时间后再重启(学名:延迟重启) ;缺点是 在TTL时间内服务相当于暂停状态;

8.基于 REDISSON 做分布式锁

================================================================================

8.1 添加maven依赖


org.redisson

redisson

3.8.2

8.2 用法:


package com.example.lock.utils;

import org.redisson.Redisson;

import org.redisson.api.RLock;

import org.redisson.api.RedissonClient;

import org.redisson.config.Config;

public class RedSession {

public void test() {

Config config = new Config();

config.useClusterServers()

.setScanInterval(2000) // cluster state scan interval in milliseconds

.addNodeAddress(“redis://127.0.0.1:7000”, “redis://127.0.0.1:7001”)

.addNodeAddress(“redis://127.0.0.1:7002”);

RedissonClient redisson = Redisson.create(config);

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

lock.lock();

try {

System.out.println(“线程已经加锁”);

} finally {

lock.unlock();

}

}

}

通过上面这段代码,我们看一下Redisson是如何基于Redis实现分布式锁的

8.3 原理解释


加锁

Redisson中提供的加锁的方法有很多,但大致类似,此处只看lock()方法

在这里插入图片描述

在这里插入图片描述

可以看到,调用getLock()方法后实际返回一个RedissonLock对象,在RedissonLock对象的lock()方法主要调用tryAcquire()方法

在这里插入图片描述

由于leaseTime == -1,于是走tryLockInnerAsync()方法,这个方法才是关键

首先,看一下evalWriteAsync方法的定义

<T, R> RFuture evalWriteAsync(String key, Codec codec, RedisCommand evalCommandType, String script, List keys, Object … params);

最后两个参数分别是keys和params

evalWriteAsync具体如何调用的呢?

commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,

"if (redis.call(‘exists’, KEYS[1]) == 0) then " +

"redis.call(‘hset’, KEYS[1], ARGV[2], 1); " +

"redis.call(‘pexpire’, KEYS[1], ARGV[1]); " +

"return nil; " +

"end; " +

"if (redis.call(‘hexists’, KEYS[1], ARGV[2]) == 1) then " +

"redis.call(‘hincrby’, KEYS[1], ARGV[2], 1); " +

"redis.call(‘pexpire’, KEYS[1], ARGV[1]); " +

"return nil; " +

"end; " +

“return redis.call(‘pttl’, KEYS[1]);”,

Collections.singletonList(getName()), internalLockLeaseTime, getLockName(threadId));

结合上面的参数声明,我们可以知道,这里KEYS[1]就是getName(),ARGV[2]是getLockName(threadId)

假设前面获取锁时传的name是“abc”,假设调用的线程ID是Thread-1,假设成员变量UUID类型的id是6f0829ed-bfd3-4e6f-bba3-6f3d66cd176c

那么KEYS[1]=abc,ARGV[2]=6f0829ed-bfd3-4e6f-bba3-6f3d66cd176c:Thread-1

因此,这段代码想表达什么呢?

1、判断有没有一个叫“abc”的key

2、如果没有,则在其下设置一个字段为“6f0829ed-bfd3-4e6f-bba3-6f3d66cd176c:Thread-1”,值为“1”的键值对 ,并设置它的过期时间

3、如果存在,则进一步判断“6f0829ed-bfd3-4e6f-bba3-6f3d66cd176c:Thread-1”是否存在,若存在,则其值加1,并重新设置过期时间

4、返回“abc”的生存时间(毫秒)

这里用的数据结构是hash,hash的结构是: key 字段1 值1 字段2 值2 。。。

用在锁这个场景下,key就表示锁的名称,也可以理解为临界资源,字段就表示当前获得锁的线程

所有竞争这把锁的线程都要判断在这个key下有没有自己线程的字段,如果没有则不能获得锁,如果有,则相当于重入,字段值加1(次数)

算法原理如下图所示:

在这里插入图片描述

解锁

protected RFuture unlockInnerAsync(long threadId) {

return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,

"if (redis.call(‘exists’, KEYS[1]) == 0) then " +

"redis.call(‘publish’, KEYS[2], ARGV[1]); " +

"return 1; " +

“end;” +

"if (redis.call(‘hexists’, KEYS[1], ARGV[3]) == 0) then " +

“return nil;” +

"end; " +

"local counter = redis.call(‘hincrby’, KEYS[1], ARGV[3], -1); " +

"if (counter > 0) then " +

"redis.call(‘pexpire’, KEYS[1], ARGV[2]); " +

"return 0; " +

"else " +

"redis.call(‘del’, KEYS[1]); " +

"redis.call(‘publish’, KEYS[2], ARGV[1]); " +

"return 1; "+

"end; " +

“return nil;”,

Arrays.asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));

}

我们还是假设name=abc,假设线程ID是Thread-1

同理,我们可以知道

KEYS[1]是getName(),即KEYS[1]=abc

KEYS[2]是getChannelName(),即KEYS[2]=redisson_lock__channel:{abc}

ARGV[1]是LockPubSub.unlockMessage,即ARGV[1]=0

ARGV[2]是生存时间

ARGV[3]是getLockName(threadId),即ARGV[3]=6f0829ed-bfd3-4e6f-bba3-6f3d66cd176c:Thread-1

因此,上面脚本的意思是:

1、判断是否存在一个叫“abc”的key

2、如果不存在,向Channel中广播一条消息,广播的内容是0,并返回1

3、如果存在,进一步判断字段6f0829ed-bfd3-4e6f-bba3-6f3d66cd176c:Thread-1是否存在

4、若字段不存在,返回空,若字段存在,则字段值减1

5、若减完以后,字段值仍大于0,则返回0

6、减完后,若字段值小于或等于0,则广播一条消息,广播内容是0,并返回1;

可以猜测,广播0表示资源可用,即通知那些等待获取锁的线程现在可以获得锁了

在这里插入图片描述

等待

上面的加锁,解锁均是 可以获取到锁资源的情况,那么当无法立即获取锁资源时,就需要等待

@Override

public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {

long threadId = Thread.currentThread().getId();

Long ttl = tryAcquire(leaseTime, unit, threadId);

// lock acquired

if (ttl == null) {

return;

}

// 订阅

RFuture future = subscribe(threadId);

commandExecutor.syncSubscription(future);

try {

while (true) {

ttl = tryAcquire(leaseTime, unit, threadId);

// lock acquired

if (ttl == null) {

break;

}

// waiting for message

if (ttl >= 0) {

getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);

} else {

getEntry(threadId).getLatch().acquire();

}

}

} finally {

unsubscribe(future, threadId);

}

// get(lockAsync(leaseTime, unit));

}

protected static final LockPubSub PUBSUB = new LockPubSub();

protected RFuture subscribe(long threadId) {

return PUBSUB.subscribe(getEntryName(), getChannelName(), commandExecutor.getConnectionManager().getSubscribeService());

}

protected void unsubscribe(RFuture future, long threadId) {

PUBSUB.unsubscribe(future.getNow(), getEntryName(), getChannelName(), commandExecutor.getConnectionManager().getSubscribeService());

}

这里会订阅Channel,当资源可用时可以及时知道,并抢占,防止无效的轮询而浪费资源

当资源可用用的时候,循环去尝试获取锁,由于多个线程同时去竞争资源,所以这里用了信号量,对于同一个资源只允许一个线程获得锁,其它的线程阻塞

9.基于 ZooKeeper 做分布式锁

=================================================================================

9.1 为什么要选择zookeeper


1、基于mysql实现分布式锁

基于分布式锁的实现,首先肯定是想单独分离出一台mysql数据库,所有服务要想操作文件(共享资源),那么必须先在mysql数据库中插入一个标志,插入标志的服务就持有了锁,并对文件进行操作,操作完成后,主动删除标志进行锁释放,其与服务会一直查询数据库,看是否标志有被占用,直到没有标志占用时自己才能写入标志获取锁。

但是这样有这么一个问题,如果服务(jvm1)宕机或者卡顿了,会一直持有锁未释放,这样就造成了死锁,因此就需要有一个监视锁进程时刻监视锁的状态,如果超过一定时间未释放就要进行主动清理锁标记,然后供其与服务继续获取锁。

如果监视锁字段进程和jvm1同时挂掉,依旧不能解决死锁问题,于是又增加一个监视锁字段进程,这样一个进程挂掉,还有另一个监视锁字段进程可以对锁进行管理。这样又诞生一个新的问题,两个监视进程必须进行同步,否则对于过期的情况管理存在不一致问题。

因此存在以下问题,并且方案变得很复杂:

1、监视锁字段进程对于锁的监视时间周期过短,仍旧会造成多售(jvm1还没处理完其持有的锁就被主动销毁,造成多个服务同时持有锁进行操作)。

2、监视锁字段进程对于锁的监视时间周期过长,会造成整个服务卡顿过长,吞吐低下。

3、监视锁字段进程间的同步问题。

4、当一个jvm持有锁的时候,其余服务会一直访问数据库查看锁,会造成其余jvm的资源浪费。

在这里插入图片描述

2、基于Redis实现分布式锁

相比较于基于数据库实现分布式锁的方案来说,基于缓存来实现在性能方面会表现的更好一点,Redis就是其中一种。由于Redis可以设置字段的有效期,因此可以实现自动释放超期的锁,不需要多个监视锁字段进程进行锁守护,可以依旧存在上述mysql实现中除了3以外1、2、4中的问题。

3、基于Zookeeper实现分布式锁

基于以上两种实现方式,有了基于zookeeper实现分布式锁的方案。由于zookeeper有以下特点:

1️⃣维护了一个有层次的数据节点,类似文件系统。

2️⃣有以下数据节点:临时节点、持久节点、临时有序节点(分布式锁实现基于的数据节点)、持久有序节点。

3️⃣zookeeper可以和client客户端通过心跳的机制保持长连接,如果客户端链接zookeeper创建了一个临时节点,那么这个客户端与zookeeper断开连接后会自动删除。

4️⃣zookeeper的节点上可以注册上用户事件(自定义),如果节点数据删除等事件都可以触发自定义事件。

5️⃣zookeeper保持了统一视图,各服务对于状态信息获取满足一致性。

Zookeeper的每一个节点,都是一个天然的顺序发号器。

在每一个节点下面创建子节点时,只要选择的创建类型是有序(EPHEMERAL_SEQUENTIAL 临时有序或者PERSISTENT_SEQUENTIAL 永久有序)类型,那么,新的子节点后面,会加上一个次序编号。这个次序编号,是上一个生成的次序编号加一

比如,创建一个用于发号的节点“/test/lock”,然后以他为父亲节点,可以在这个父节点下面创建相同前缀的子节点,假定相同的前缀为“/test/lock/seq-”,在创建子节点时,同时指明是有序类型。如果是第一个创建的子节点,那么生成的子节点为/test/lock/seq-0000000000,下一个节点则为/test/lock/seq-0000000001,依次类推,等等。

在这里插入图片描述

9.2 如何使用zookeeper实现分布锁


大致思想为:每个客户端对某个方法加锁时,在 Zookeeper 上与该方法对应的指定节点的目录下,生成一个唯一的临时有序节点。

判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个临时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。

排它锁

排他锁,又称写锁或独占锁。如果事务T1对数据对象O1加上了排他锁,那么在整个加锁期间,只允许事务T1对O1进行读取或更新操作,其他任务事务都不能对这个数据对象进行任何操作,直到T1释放了排他锁。

排他锁核心是保证当前有且仅有一个事务获得锁,并且锁释放之后,所有正在等待获取锁的事务都能够被通知到。

Zookeeper 的强一致性特性,能够很好地保证在分布式高并发情况下节点的创建一定能够保证全局唯一性,即Zookeeper将会保证客户端无法重复创建一个已经存在的数据节点。可以利用Zookeeper这个特性,实现排他锁。

1️⃣定义锁:通过Zookeeper上的数据节点来表示一个锁

2️⃣获取锁:客户端通过调用 create 方法创建表示锁的临时节点,可以认为创建成功的客户端获得了锁,同时可以让没有获得锁的节点在该节点上注册Watcher监听,以便实时监听到lock节点的变更情况

3️⃣释放锁:以下两种情况都可以让锁释放

当前获得锁的客户端发生宕机或异常,那么Zookeeper上这个临时节点就会被删除

正常执行完业务逻辑,客户端主动删除自己创建的临时节点

基于Zookeeper实现排他锁流程:

在这里插入图片描述

共享锁

共享锁,又称读锁。如果事务T1对数据对象O1加上了共享锁,那么当前事务只能对O1进行读取操作,其他事务也只能对这个数据对象加共享锁,直到该数据对象上的所有共享锁都被释放。

共享锁与排他锁的区别在于,加了排他锁之后,数据对象只对当前事务可见,而加了共享锁之后,数据对象对所有事务都可见。

1️⃣定义锁:通过Zookeeper上的数据节点来表示一个锁,是一个类似于 /lockpath/[hostname]-请求类型-序号 的临时顺序节点

2️⃣获取锁:客户端通过调用 create 方法创建表示锁的临时顺序节点,如果是读请求,则创建 /lockpath/[hostname]-R-序号 节点,如果是写请求则创建 /lockpath/[hostname]-W-序号节点

3️⃣判断读写顺序:大概分为4个步骤

  • 1)创建完节点后,获取 /lockpath 节点下的所有子节点,并对该节点注册子节点变更的Watcher监听

  • 2)确定自己的节点序号在所有子节点中的顺序

  • 3.1)对于读请求:1. 如果没有比自己序号更小的子节点,或者比自己序号小的子节点都是读请求,那么表明自己已经成功获取到了共享锁,同时开始执行读取逻辑 2. 如果有比自己序号小的子节点有写请求,那么等待

  • 3.2)对于写请求,如果自己不是序号最小的节点,那么等待

  • 4)接收到Watcher通知后,重复步骤1)

4️⃣释放锁:与排他锁逻辑一致

在这里插入图片描述

在这里插入图片描述

羊群效应

在实现共享锁的 “判断读写顺序” 的第1个步骤是:创建完节点后,获取 /lockpath 节点下的所有子节点,并对该节点注册子节点变更的Watcher监听。这样的话,任何一次客户端移除共享锁之后,Zookeeper将会发送子节点变更的Watcher通知给所有机器,系统中将有大量的 “Watcher通知” 和 “子节点列表获取” 这个操作重复执行,然后所有节点再判断自己是否是序号最小的节点(写请求)或者判断比自己序号小的子节点是否都是读请求(读请求),从而继续等待下一次通知。

然而,这些重复操作很多都是 “无用的”,实际上每个锁竞争者只需要关注序号比自己小的那个节点是否存在即可。

当集群规模比较大时,这些 “无用的” 操作不仅会对Zookeeper造成巨大的性能影响和网络冲击,更为严重的是,如果同一时间有多个客户端释放了共享锁,Zookeeper服务器就会在短时间内向其余客户端发送大量的事件通知–这就是所谓的 “羊群效应”。

改进分布锁的实现

改进后的分布式锁实现:

  • 1.客户端调用 create 方法创建一个类似于 /lockpath/[hostname]-请求类型-序号 的临时顺序节点。

  • 2.客户端调用 getChildren 方法获取所有已经创建的子节点列表(这里不注册任何Watcher)。

  • 3.如果无法获取任何共享锁,那么调用 exist 来对比自己小的那个节点注册Watcher

读请求:向比自己序号小的最后一个写请求节点注册Watcher监听

写请求:向比自己序号小的最后一个节点注册Watcher监听

  • 4.等待Watcher监听,继续进入步骤2

Zookeeper羊群效应改进前后Watcher监听图:

在这里插入图片描述

ZOOKEEPER 锁相关基础知识

  • zk 一般由多个节点构成(单数),采用 zab 一致性协议。因此可以将 zk 看成一个单点结构,对其修改数据其内部自动将所有节点数据进行修改而后才提供查询服务。

  • zk 的数据以目录树的形式,每个目录称为 znode, znode 中可存储数据(一般不超过 1M),还可以在其中增加子节点。

  • 子节点有三种类型。序列化节点,每在该节点下增加一个节点自动给该节点的名称上自增。临时节点,一旦创建这个 znode 的客户端与服务器失去联系,这个 znode 也将自动删除。最后就是普通节点。

  • Watch 机制,client 可以监控每个节点的变化,当产生变化会给 client 产生一个事件。

ZK 基本锁

  • 原理:利用临时节点与 watch 机制。每个锁占用一个普通节点 /lock,当需要获取锁时在 /lock 目录下创建一个临时节点,创建成功则表示获取锁成功,失败则 watch/lock 节点,有删除操作后再去争锁。临时节点好处在于当进程挂掉后能自动上锁的节点自动删除即取消锁。

  • 缺点:所有取锁失败的进程都监听父节点,很容易发生羊群效应,即当释放锁后所有等待进程一起来创建节点,并发量很大。

ZK 锁优化

  • 原理:上锁改为创建临时有序节点,每个上锁的节点均能创建节点成功,只是其序号不同。只有序号最小的可以拥有锁,如果这个节点序号不是最小的则 watch 序号比本身小的前一个节点 (公平锁)。

  • 步骤:

1.在 /lock 节点下创建一个有序临时节点 (EPHEMERAL_SEQUENTIAL)。

2.判断创建的节点序号是否最小,如果是最小则获取锁成功。不是则取锁失败,然后 watch 序号比本身小的前一个节点。

3.当取锁失败,设置 watch 后则等待 watch 事件到来后,再次判断是否序号最小。

4.取锁成功则执行代码,最后释放锁(删除该节点)。

代码实现

  • 1.添加maven依赖

org.apache.zookeeper

zookeeper

3.7.0

  • 2.代码实现 如下:

import org.apache.zookeeper.*;

import org.apache.zookeeper.data.Stat;

import java.io.IOException;

import java.util.ArrayList;

import java.util.Collections;

import java.util.List;

import java.util.concurrent.CountDownLatch;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.Lock;

/**

  • 基于zookeeper的分布式锁

*/

public class DistributedLock implements Lock, Watcher {

private ZooKeeper zk = null;

// 根节点

private String ROOT_LOCK = “/lock_msb”;

// 竞争的资源

private String lockName;

// 等待的前一个锁

private String WAIT_LOCK;

// 当前锁

private String CURRENT_LOCK;

// 计数器

private CountDownLatch countDownLatch;

private int sessionTimeout = 3000000;

private List exceptionList = new ArrayList();

/**

  • 配置分布式锁

  • @param config 连接的url

  • @param lockName 竞争资源

*/

public DistributedLock(String config, String lockName) {

this.lockName = lockName;

try {

// 连接zookeeper

zk = new ZooKeeper(config, sessionTimeout, this);

Stat stat = zk.exists(ROOT_LOCK, false);

if (stat == 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();
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

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

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

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

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

现在正是金三银四的春招高潮,前阵子小编一直在搭建自己的网站,并整理了全套的**【一线互联网大厂Java核心面试题库+解析】:包括Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等**

image
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!
nterruptedException e) {

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

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-wUsxIAjP-1711985082975)]

[外链图片转存中…(img-iw0ayso0-1711985082975)]

[外链图片转存中…(img-BSSj7Vnv-1711985082976)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

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

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

现在正是金三银四的春招高潮,前阵子小编一直在搭建自己的网站,并整理了全套的**【一线互联网大厂Java核心面试题库+解析】:包括Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等**

[外链图片转存中…(img-zwehlsLr-1711985082976)]
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

  • 24
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java分布式实现方式有多种,常见的包括: 1. 基于Redis的分布式:利用Redis单线程的特性,使用SETNX命令创建,利用EXPIRE设置的过期时间,同时使用DEL命令释放,确保的释放是原子的。 2. 基于Zookeeper的分布式:通过创建临时节点实现分布式,当某个服务占用了,其它服务将无法创建同名节点,从而保证同一时间只有一个服务占用该。 3. 基于数据库的分布式:使用数据库表中的一行记录来表示状态,使用事务确保的获取和释放是原子的。 4. 基于Redisson的分布式:Redisson是一个开源的Java分布式框架,提供了对分布式的支持,使用SETNX和EXPIRE命令实现的创建和过期,同时还提供了自旋、可重入等高级特性。 以上是Java分布式实现方式的几种常见方式,不同的实现方式有着各自的特点和适用场景,需要根据实际需求进行选择。 ### 回答2: Java分布式分布式系统中实现数据同步和控制的关键技术之一,它用于保证多个分布式进程并发访问共享资源时的数据一致性和安全性。分布式与普通的相比,需要解决跨进程、跨节点的同步和并发控制问题。 Java分布式的实现方式有以下几种: 1. 基于Zookeeper实现分布式 Zookeeper是一个高性能的分布式协调服务,它可以被用来实现分布式。Zookeeper的实现原理是基于它的强一致性和顺序性,可以保证多个进程访问同一个分布式时的数据同步和控制。 通过创建一个Zookeeper的持久节点来实现分布式,使用create()方法来创建节点,如果创建成功则说明获取成功。当多个进程同时请求获取时,只有一个进程能够创建节点成功,其它进程只能等待。当持有分布式的进程退出时,Zookeeper会自动删除对应的节点,其它进程就可以继续请求获取。 2. 基于Redis实现分布式 Redis是高性能的内存数据库,可以使用它的setnx()命令来实现分布式。setnx()命令可以在指定的key不存在时设置key的值,并返回1;如果key已经存在,则返回0。通过这个原子性的操作来实现分布式。 当多个进程同时请求获取时,只有一个进程能够成功执行setnx()命令,其它进程只能等待。进程在持有期间,可以利用Redis的expire()命令来更新的过期时间。当持有分布式的进程退出时,可以通过delete()命令来删除。 3. 基于数据库实现分布式 数据库通过ACID特性来保证数据的一致性、并发性和可靠性,可以通过在数据库中创建一个唯一索引来实现分布式。当多个进程同时请求获取时,只有一个进程能够成功插入唯一索引,其它进程只能等待。当持有分布式的进程退出时,可以通过删除索引中对应的记录来释放。 不同的实现方式各有优劣。基于Zookeeper的实现方式可以保证分布式的一致性和可靠性,但是需要引入额外的依赖;基于Redis可以实现较高性能的分布式,但是在高并发条件下可能会存在死等问题;基于数据库的实现方式简单,但在高并发条件下也可能会有争抢等问题。 总之,在选择分布式的实现方式时,需要根据业务场景和需求来综合考虑各种因素,选择最适合自己的方式。 ### 回答3: 分布式系统中的并发控制是解决分布式系统中竞争资源的重要问题之一,而分布式作为一种并发控制工具,在分布式系统中被广泛采用。Java作为一种常用的编程语言,在分布式的实现方面也提供了多种解决方案。下面就分别介绍Java分布式的实现方式。 1. 基于ZooKeeper的分布式 ZooKeeper是分布式系统中常用的协调工具,其提供了一套完整的API用于实现分布式。实现分布式的过程中需要创建一个Znode,表示,同时用于控制数据的访问。在这个Znode上注册监听器用于接收释放的成功/失败事件,从而控制加/解的过程。 2. 基于Redis的分布式 Redis作为一种高性能的Key-Value数据库,其提供了完整的API用于实现分布式。实现分布式的过程中需要在Redis中创建一个Key,利用Redis的SETNX命令进行加,同时设置过期时间保证的生命周期。在解时需要判断是否持有并删除对应的Key。 3. 基于数据库的分布式 数据库作为分布式系统中常用的数据存储方式,其提供了事务机制用于实现分布式。在实现分布式的过程中需要在数据库中创建一个表,利用数据库的事务机制实现加/解,同时需要设置过期时间保证的生命周期。 总之,以上三种方式都是常用的Java分布式的实现方式。选择合适的方法需要综合考虑的使用场景、性能需求、可靠性要求等因素。同时,在实现分布式的过程中需要注意的加/解的正确性和过期时间的设置,保证分布式系统的并发控制的正确性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值