多线程并发问题
Posted benniaoxianfei
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程并发问题相关的知识,希望对你有一定的参考价值。
应用场景:库存修改
一:使用 synchronized ,lock 等同步方法:
(1)特点:synchronized的flag只有jvm进程内可见,不能跨jvm
(2)缺点:1.作用范围是单个jvm实例, 如果做了集群,分布式等,就没用了;
2.数据库的事务隔离级别,加锁时机。主要矛盾是事务开启和提交的时机与加锁解锁时机不一致。
①Repeatable Read级别:事务开启后,不会读取到其他事务提交的数据.
当T1执行完后,T2执行,读取不到T1提交的数据,所以会出问题。
②Read Committed级别:事务开启后,可以读取到其他事务提交的数据。
a.开启事务(aop);
b.加锁(进入synchronized方法);
c.释放锁(退出synchronized方法);
d.提交事务(aop).
可以看出是先释放锁,再提交事务.所以T2执行查询,可能还是未读到T1提交的数据,还会出问题。
(3)伪代码:
1 public synchronized void buy(String productName, Integer buyQuantity) { 2 Product product = 从数据库查询出记录; 3 if (product.getSurplus < buyQuantity) { 4 return "库存不足"; 5 } 6 // set新的剩余数量 7 product.setSurplus(product.getSurplus() - quantity); 8 // 更新数据库 9 update(product); 10 // 记录日志... 11 // 其他业务... 12 } 13
(4)改善:
1.在事务开启前加锁,事务提交后解锁------相当于事务串行化;
2.将查询库存,扣减库存这2步操作,单独提取个方法,单独使用事务,并且事务隔离级别设置为RC;
3.单独的这个方法,需要放到另外的service类中;
4.因为使用spring,同一个bean的内部方法调用,是不会被再次代理的,所以配置的单独事务等需要放到另外的service bean 中。
二:不查询,直接更新
(1)缺点:1.不能跨jvm;
2.不具备通用性,例如add操作。
(2)伪代码:
1 public synchronized void buy(String productName, Integer buyQuantity) { 2 // 其他校验... 3 int 影响行数 = update table set surplus = (surplus - buyQuantity) where id = 1 and (surplus - buyQuantity) > 0 ; 4 if (result < 0) { 5 return "库存不足"; 6 } 7 // 记录日志...// 其他业务... 8 } 9
三:CAS (乐观锁)
(1)特点:数据库的事务隔离级别必须是RC。
(2)注意:1.失败重试次数,是否需要限制;
2.失败重试对用户是透明的;
3.CAS中经典问题------ABA的问题(解决:version)。
(3)伪代码:
1 update t set surplus = 90 ,version = version+1 where id = x and version = oldVersion ;
四:数据库锁(悲观锁 )
(1)特点:1.在查询数据的时候,就将数据锁住.事务串行化;
2.select for update 的flag 是全局可见,可以跨jvm。
(2)原理:1.线程T1 进行sub , 查询库存剩余 100;
2.线程T2 进行sub , 这时候,线程T1事务还未提交,线程T2阻塞,直到线程T1事务提交或回滚才能查询出结果;
3.所以线程T2查询出的一定是最新的数据.相当于事务串行化了,就解决了数据一致性问题。
(3)注意:1.统一入口:所有库存操作都需要统一使用 select for update ,这样才会阻塞, 如果另外一个方法还是普通的select, 是不会被阻塞的;
2.加锁顺序:如果有多个锁,那么加锁顺序要一致,否则会出现死锁。
(4)伪代码:
1 Product product = select * from table where name = productName for update; 2 if (查询的剩余数量 > buyQuantity) { 3 影响行数 = update table set surplus = (surplus - buyQuantity) where name = productName ; 4 } else { 5 return "库存不足"; 6 }
五:分布式锁(zookeeper,redis等)
(1)特点:分布式锁的flag是全局可见,可以跨jvm。
(2)伪代码:
1 //获取锁 2 String result = jedis.set(key, value, "NX", "PX", expireMillis); 3 if (result != null && result.equalsIgnoreCase("OK")) { 4 flag = true; 5 } 6 7 //释放锁 8 String script = "if redis.call(‘get‘, KEYS[1]) == ARGV[1] then return redis.call(‘del‘, KEYS[1]) else return 0 end"; 9 Object result = jedis.eval(script, Collections.singletonList(fullKey), Collections.singletonList(value)); 10 if (Objects.equals(UNLOCK_SUCCESS, result)) { 11 flag = true; 12 } 13 14
以上是关于多线程并发问题的主要内容,如果未能解决你的问题,请参考以下文章