多线程并发问题

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   

 

以上是关于多线程并发问题的主要内容,如果未能解决你的问题,请参考以下文章

线程学习知识点总结

Java多线程与并发库高级应用-工具类介绍

Java多线程与并发库高级应用-工具类介绍

多线程

cpp►多线程

python中的多线程和多进程编程