ToB企服应用市场:ToB评测及商务社交产业平台

标题: 利用MySQL中的乐观锁和悲观锁实现分布式锁 [打印本页]

作者: 郭卫东    时间: 2022-8-21 23:06
标题: 利用MySQL中的乐观锁和悲观锁实现分布式锁
背景

对于一些并发量不是很高的场景,使用MySQL的乐观锁实现会比较精简且巧妙。
下面就一个小例子,针对不加锁、乐观锁以及悲观锁这三种方式来实现。
主要是一个用户表,它有一个年龄的字段,然后并发地对其加一,看看结果是否正确。
一些基础实现类
  1. @Data
  2. @Builder
  3. @AllArgsConstructor
  4. @NoArgsConstructor
  5. public class User {
  6.     private Integer age;
  7.     private String name;
  8.     private Long id;
  9.     private Long version;
  10. }
  11. public interface UserMapper {
  12.     @Results(value = {
  13.             @Result(property = "id", column = "id", javaType = Long.class, jdbcType = JdbcType.BIGINT),
  14.             @Result(property = "age", column = "age", javaType = Integer.class, jdbcType = JdbcType.INTEGER),
  15.             @Result(property = "name", column = "name", javaType = String.class, jdbcType = JdbcType.VARCHAR),
  16.             @Result(property = "version", column = "version", javaType = Long.class, jdbcType = JdbcType.BIGINT),
  17.     })
  18.     @Select("SELECT id, age, name, version FROM user WHERE id = #{id}")
  19.     User getUser(Long id);
  20.     @Update("UPDATE user SET age = #{age}, version=version+1 WHERE id = #{id} AND version = #{version}")
  21.     Boolean compareAndSetAgeById(Long id, Long version, Integer age);
  22.     @Update("UPDATE user SET age = #{age} WHERE id = #{id}")
  23.     Boolean setAgeById(Long id, Integer age);
  24.     @Select("SELECT id, age, name, version FROM user WHERE id = #{id} for update")
  25.     User getUserForUpdate(Long id);
  26. }
  27. private static void exe(CountDownLatch countDownLatch, int threads, Runnable runnable) {
  28.     ExecutorService executorService = Executors.newCachedThreadPool();
  29.     for (int i = 0; i < threads; i++) {
  30.         executorService.execute(() -> {
  31.             runnable.run();
  32.             countDownLatch.countDown();
  33.         });
  34.     }
  35.     executorService.shutdown();
  36. }
  37. private static User getUser(long id) {
  38.     try (SqlSession session = openSession(getDataSource1())) {
  39.         UserMapper userMapper = session.getMapper(UserMapper.class);
  40.         return userMapper.getUser(id);
  41.     }
  42. }
  43. public static SqlSession openSession(DataSource dataSource) {
  44.     TransactionFactory transactionFactory = new JdbcTransactionFactory();
  45.     Environment environment = new Environment("development", transactionFactory, dataSource);
  46.     Configuration configuration = new Configuration(environment);
  47.     configuration.addMapper(UserMapper.class);
  48.     configuration.setCacheEnabled(false);
  49.     SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
  50.     return sqlSessionFactory.openSession();
  51. }
  52. private static DataSource getDataSource1() {
  53.     MysqlConnectionPoolDataSource dataSource = new MysqlConnectionPoolDataSource();
  54.     dataSource.setUser("root");
  55.     dataSource.setPassword("root");
  56.     dataSource.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8");
  57.     return dataSource;
  58. }
复制代码
不加锁
  1. private static Boolean addAge(long id, int value) {
  2.     Boolean result;
  3.     try (SqlSession session = openSession(getDataSource1())) {
  4.         UserMapper userMapper = session.getMapper(UserMapper.class);
  5.         User user = userMapper.getUser(id);
  6.         result = userMapper.setAgeById(user.getId(), user.getAge() + value);
  7.         session.commit();
  8.     }
  9.     return result;
  10. }
  11. public static void main(String[] args) throws Exception {
  12.     long id = 1L;
  13.     int threads = 50;
  14.     CountDownLatch countDownLatch = new CountDownLatch(threads);
  15.     log.info("user:{}", getUser(id));
  16.     long start = System.currentTimeMillis();
  17.     exe(countDownLatch, threads, () -> addAge(1, 1));
  18.     countDownLatch.await();
  19.     long end = System.currentTimeMillis();
  20.     log.info("end - start : {}", end - start);
  21.     log.info("user:{}", getUser(id));
  22. }
复制代码
  1. 865  [main] INFO  cn.eagle.li.mybatis.UpdateMain - user:User(age=0, name=3, id=1, version=0)
  2. 1033 [main] INFO  cn.eagle.li.mybatis.UpdateMain - end - start : 164
  3. 1046 [main] INFO  cn.eagle.li.mybatis.UpdateMain - user:User(age=9, name=3, id=1, version=0)
复制代码
从输出可以看出,50个并发,但是执行成功的只有9个,这种实现很明显是有问题的。
乐观锁
  1. private static Boolean compareAndAddAge(long id, int value, int times) {
  2.     int time = 0;
  3.     Boolean result = false;
  4.     while (time++ < times && BooleanUtils.isFalse(result)) {
  5.         result = compareAndAddAge(id, value);
  6.     }
  7.     return result;
  8. }
  9. private static Boolean compareAndAddAge(long id, int value) {
  10.     try (SqlSession session = openSession(getDataSource1())) {
  11.         UserMapper userMapper = session.getMapper(UserMapper.class);
  12.         User user = userMapper.getUser(id);
  13.         Boolean result = userMapper.compareAndSetAgeById(id, user.getVersion(), user.getAge() + value);
  14.         session.commit();
  15.         return result;
  16.     }
  17. }
  18. public static void main(String[] args) throws Exception {
  19.     long id = 1L;
  20.     int threads = 50;
  21.     CountDownLatch countDownLatch = new CountDownLatch(threads);
  22.     log.info("user:{}", getUser(id));
  23.     long start = System.currentTimeMillis();
  24.     exe(countDownLatch, threads, () -> addAge(1, 1, 20));
  25.     countDownLatch.await();
  26.     long end = System.currentTimeMillis();
  27.     log.info("end - start : {}", end - start);
  28.     log.info("user:{}", getUser(id));
  29. }
复制代码
  1. 758  [main] INFO  cn.eagle.li.mybatis.UpdateMain - user:User(age=0, name=3, id=1, version=0)
  2. 1270 [main] INFO  cn.eagle.li.mybatis.UpdateMain - end - start : 509
  3. 1277 [main] INFO  cn.eagle.li.mybatis.UpdateMain - user:User(age=50, name=3, id=1, version=50)
复制代码
从输出可以看出,并发的情况下,结果是没问题的。
悲观锁
  1. private static Boolean addAgeForUpdate(long id, int value) {
  2.     Boolean result;
  3.     try (SqlSession session = openSession(getDataSource1())) {
  4.         UserMapper userMapper = session.getMapper(UserMapper.class);
  5.         User user = userMapper.getUserForUpdate(id);
  6.         result = userMapper.setAgeById(id, user.getAge() + value);
  7.         session.commit();
  8.     }
  9.     return result;
  10. }
  11. public static void main(String[] args) throws Exception {
  12.     long id = 1L;
  13.     int threads = 50;
  14.     CountDownLatch countDownLatch = new CountDownLatch(threads);
  15.     log.info("user:{}", getUser(id));
  16.     long start = System.currentTimeMillis();
  17.     exe(countDownLatch, threads, () -> addAgeForUpdate(1, 1));
  18.     countDownLatch.await();
  19.     long end = System.currentTimeMillis();
  20.     log.info("end - start : {}", end - start);
  21.     log.info("user:{}", getUser(id));
  22. }
复制代码
  1. 631  [main] INFO  cn.eagle.li.mybatis.UpdateMain - user:User(age=0, name=3, id=1, version=50)
  2. 829  [main] INFO  cn.eagle.li.mybatis.UpdateMain - end - start : 196
  3. 837  [main] INFO  cn.eagle.li.mybatis.UpdateMain - user:User(age=50, name=3, id=1, version=50)
复制代码
从输出可以看出,并发的情况下,结果是没问题的。
总结

从以上来看,乐观锁和悲观锁实现都是没有问题的,至于选哪一种,还是要看业务的场景,比如说并发量的多少,加锁时长等等。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4