高并发下的锁机制

最近接触了一个项目,甲方要求要有5000的高并发,所以也就开始慢慢的接触到了高并发的这一领域。

所谓高并发,就是在同一时间内有大量的请求来访,而我们后台不仅要在满足响应时间的前提下保证数据的一致性。因为我们Java是多线程的形式来处理这些请求的,那就意味着可能我这一线程原先的数据被另一线程篡改了,变成了他的数据,所以导致我在持久化的时候,数据不正确,也有可能我在数据库里查到的库存不为0,但当我执行到下单持久化的时候,有人先执行了下单,也就意味着现在库存是发生变化的了,那就有可能到我插入订单的时候,库存就为负数了。

高并发最为常见的一种情况就是 “下单模板” 。

而解决高并发的方法可以从三个层面上去解决:架构层面:服务器上使用ngnix负载均衡,Java使用spring cloud微服务拆分。代码层面:代码优化,算法优化,少new一些对象什么的。数据库层面:查询优化,读写分离,分库分表等。

而我们这次主要是介绍代码方面的。实现的方式也有多种,最为简单的便是加锁机制,一个简单的synchronized同步关键字,便能使异步操作变为同步。使多线程操作变成单线程执行。

但当你的下单程序可能不止一个接口,或者是并发量过高,synchronized压不住,这时候便可以考虑数据库加锁或者使用redis分布锁,以及结合reids 和消息队列的形式。这种方式,这里有传送门,介绍的很详细:https://blog.csdn.net/ZHJUNJUN93/article/details/78560700

而对于数据库来说的话:锁机制 有 乐观锁,悲观锁:排他锁(X锁),共享锁(S锁)

因为对于mysql来说的话,在update,insert,delete的时候都会自动给其加上排他锁,而select的时候就没有自动上锁的功能,也就是说,我们要做的锁机制便是针对select查询时使用。

所谓乐观锁:便是乐天派,他认为一切的操作都不会有冲突操作。这种锁一般的并发量较小的情况下使用,同时乐观锁不像其他锁一样有mysql专门的语句,这种锁的实现需要我们自己来实现。使用方法:手动在我们查询的表里加上一个字段(例如version),每当我们的更改一此数据(例如:库存)我们就手动给这个字段加上1.这样我们在查询库存的时候,同时把这个字段的值也取出来,在更新操作的时候则加上这个限制条件 where uuid = #{uuid} and version= #{version} 这样的话,如果修改成功的话,便是正常的,否之异常

而悲观锁的话:故名思意。其下游排他锁和共享锁。排他锁:写的时候不能读。共享锁:读的时候不能写 这两种锁都有专门的MySQL语句,同时都要在事务下执行。记得先 set autocommit = 0; 关闭事务的自动提交。

共享锁lock in share mode

排他锁:这种锁机制应该应用的相对多一些,具体操作:

BEGIN WORK; select .... for update;

业务逻辑判断:是否大于0之类的

update .... commit work;

记得在判断不符合条件的时候也要 commit work;语句执行一下,避免有异常回滚的时候造成数据的混乱。同时mysql回滚的时候也是会提交事务。

因为我自己在做实验的时候,经常出现了死锁的情况:这里提供一下MySQL死锁的解决方案:

在mysql查询中执行以下指令

select * from information_schema.innodb_trx

找出与其他几个有参数不同的线程,找出他的线程号mysql_thread_id

执行命令kill +线程号即可解决。

另外补充一下:在java里的HashMap 和ArrayList都是线程不安全的。对于list的话,推荐在synchronized中生成ArrayList

而对于Map的话则可以使用new Hashtable<>();或者Collections.synchronizedMap(new HashMap<String, String>());或者 new ConcurrentHashMap<>();

推荐使用 new ConcurrentHashMap<>(); 

因为其效率最高。

同时我们的Integer这些也是线程不安全的,我们可以使用Java的Atomic包来生成。

猜你喜欢

转载自blog.csdn.net/weixin_39797865/article/details/82109132
今日推荐