Java封装同步锁(完整版)

大家好这是一个普通的 controller

然后呢这一段代码被加了一个同步锁

意味着同一时刻

只有一个线程可以执行进来

但是呢这样有点问题

我们想要的效果是

同一个订单才会加一个同步锁

不同的订单之间我们不需要同步(执行)

他可以并行

那我们先来试一下现在的效果哈

我们来一个不同的订单

一个是1、一个是2

大家看只有1结束了2才开始

那有的同学就会想到

我们可不可以直接把 orderId 放进来

我们来试一下

一个1、一个2

大家看

哎可以同时开始了

但是我们再换一个

我们用两个1

清一下(控制台)。我们来试一下

大家看他两个1是同时开始的

原因是啊这个 synchronized 的这里边

比较的并不是字符串的值

而是说这个对象

那很显然

每一次请求进来的这个对象

一定不是同一个对象

所以呢解决这个问题

我们可以在这加一个intern

也就是说:把这个字符串的值

放到字符串常量池里面

这样的话呢他每次是同一个对象

我们来试一下

我们用两个1 大家看啊

大家看!这个是锁住了,两个1没问题

然后呢我们再用一个1、一个2

一个1 一个2注意看

这1和2是同时开始的,也就是说

这样写的话

效果是满足了我们想要的效果

但是呢这个写法又不是很推荐

具体的原因我们后边在讨论哈

那所以除了这个写法

我们还可以有另一种写法

就是我们在这写一个Map

然后呢自己来缓存一些锁

我们就叫mutexCache

我们先用 HashMap

然后呢

我们在这尝试从这个缓存里取一把锁

这是 gaito 的 id

我们就叫 mochas

four key

然后呢

肯定要判断这个 mutex4Key 是不是空

如果为空的话

mutexKey 等于 new 一个

然后再把这个 mutex 放进去

缓存里边

orderId

然后 mutex4Key

然后呢我们在这

用这个 mutex4Key 来作为锁

最终呢我们这里边要把它释放掉

mutexCache.remove

orderId 把这个锁释放掉

但是呢正常来说这应该放到 try ... finally

我们先这样写哈

然后我们再来试一下

好我们先来试一下

两个001

好注意看

好锁住了对吧

第一个结束了第二个才开始

我们再试来

001、002

一个1、一个2

大家看

这两个是同时开始的

也同时结束的

也说看上去这样就可以了

但是

他还是有问题

为什么呢因为

这一段代码没有加同步锁

那意味着极端情况下同一个订单的

两个线程可能会同时执行这段代码

那第一个稍微快一点刚执行到这

第二个也去取了

也就是说:第一个还没放进去

第二个一取他还是取不到锁

我们先来验证一下这个情况哈

我把这再清一下

那这样的话我们就不能用终端了

因为我的手的速度赶不上

所以呢我们可以借助这个jmeter工具

我把这个

请求都写好了

都是请求001

然后呢用100个并发来试一下

100个并发没有问题

把它停一下

还有(没执行完的),还有我们重启一下服务哈

我们用1,000个并发

注意看

1,000个并发也没有没有问题

我们再来一遍

再增加

1,000个没问题我们用5,000个

大家看

他就已经有很多线程是同时开始的了

就说在5,000个并发的时候

他就锁不住同一个订单了

那我们再来优化一下哈

我先把这个停一下

然后呢我们把程序优化一下

我们把这加一个线程锁

这回用this就行了

我们不管他是不是同一订单都要

同步执行这段代码

啊这个作用域(不对)

好再来启动一遍

然后呢我把jmeter再启动起来

好现在是5,000个并发

然后这个请求都是001

我们再来试一遍哈

大家看!它就不会再出现那种情况了

把它停掉哈

还是得停一会

服务停下

我还是把它强退吧

那我们针对这段代码其实还可以

有另一种写法

就是说我们把这个 map 换成

ConcurrentHashMap

这个是线程安全的

然后注意

这一段代码我们可以这样写

点 computeIfAbsent

然后呢把 orderId 拿过来

如果值不存在的话 我们new一个

new一个 Object 就好了

然后呢把它放到这个 mutex4key

也就是说:这一段代码的作用

和这个逻辑是一样的

我们拿 key 去取值

取不到的话new一个

而且这个 ConcurrentHashMap

是现场安全的

(所以)这一句代码不需要加同步锁了

所以呢我们就可以这样把这段去掉

好再来试一下

我们这边用1万个注意看啊

开始

大家看!他也不会有

问题也是一个一个来的

好,我再停一下

那到这里呢我们把功能就实现了

但是呢考虑到公用性

我们可以把它进一步封装一下

就说我们可以封装这么一个工具类

然后呢

准备这么一个 map

然后在这里边写一个方法叫 exec

让他传一个 runnable 进来

这样的话还是刚才那个代码断

只不过把这个 runnable

放在这执行一下

然后最终它替我们

管理这些锁

那使用起来呢就是我们把这个类

首先注入到这个 controller 里边

然后呢我们用这个来实现

把这些

代替掉

点 exec

然后 orderId

然后呢写一个箭头函数

这样

然后把这个逻辑放在这里边来执行

这样就好了

这个就不需要了

这些都不需要了

好这样就可以了

再见

最后更新于