背景

对于一些并发量不是很高的场景,使用MySQL的乐观锁实现会比较精简且巧妙。

下面就一个小例子,针对不加锁、乐观锁以及悲观锁这三种方式来实现。

主要是一个用户表,它有一个年龄的字段,然后并发地对其加一,看看结果是否正确。

一些基础实现类

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer age;
private String name;
private Long id;
private Long version;
} public interface UserMapper {
@Results(value = {
@Result(property = "id", column = "id", javaType = Long.class, jdbcType = JdbcType.BIGINT),
@Result(property = "age", column = "age", javaType = Integer.class, jdbcType = JdbcType.INTEGER),
@Result(property = "name", column = "name", javaType = String.class, jdbcType = JdbcType.VARCHAR),
@Result(property = "version", column = "version", javaType = Long.class, jdbcType = JdbcType.BIGINT),
})
@Select("SELECT id, age, name, version FROM user WHERE id = #{id}")
User getUser(Long id); @Update("UPDATE user SET age = #{age}, version=version+1 WHERE id = #{id} AND version = #{version}")
Boolean compareAndSetAgeById(Long id, Long version, Integer age); @Update("UPDATE user SET age = #{age} WHERE id = #{id}")
Boolean setAgeById(Long id, Integer age); @Select("SELECT id, age, name, version FROM user WHERE id = #{id} for update")
User getUserForUpdate(Long id);
} private static void exe(CountDownLatch countDownLatch, int threads, Runnable runnable) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < threads; i++) {
executorService.execute(() -> {
runnable.run();
countDownLatch.countDown();
});
}
executorService.shutdown();
} private static User getUser(long id) {
try (SqlSession session = openSession(getDataSource1())) {
UserMapper userMapper = session.getMapper(UserMapper.class);
return userMapper.getUser(id);
}
} public static SqlSession openSession(DataSource dataSource) {
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(UserMapper.class);
configuration.setCacheEnabled(false);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
return sqlSessionFactory.openSession();
} private static DataSource getDataSource1() {
MysqlConnectionPoolDataSource dataSource = new MysqlConnectionPoolDataSource();
dataSource.setUser("root");
dataSource.setPassword("root");
dataSource.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8");
return dataSource;
}

不加锁

private static Boolean addAge(long id, int value) {
Boolean result;
try (SqlSession session = openSession(getDataSource1())) {
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.getUser(id);
result = userMapper.setAgeById(user.getId(), user.getAge() + value);
session.commit();
}
return result;
} public static void main(String[] args) throws Exception {
long id = 1L;
int threads = 50;
CountDownLatch countDownLatch = new CountDownLatch(threads);
log.info("user:{}", getUser(id));
long start = System.currentTimeMillis();
exe(countDownLatch, threads, () -> addAge(1, 1));
countDownLatch.await();
long end = System.currentTimeMillis();
log.info("end - start : {}", end - start);
log.info("user:{}", getUser(id));
}
865  [main] INFO  cn.eagle.li.mybatis.UpdateMain - user:User(age=0, name=3, id=1, version=0)
1033 [main] INFO cn.eagle.li.mybatis.UpdateMain - end - start : 164
1046 [main] INFO cn.eagle.li.mybatis.UpdateMain - user:User(age=9, name=3, id=1, version=0)

从输出可以看出,50个并发,但是执行成功的只有9个,这种实现很明显是有问题的。

乐观锁

private static Boolean compareAndAddAge(long id, int value, int times) {
int time = 0;
Boolean result = false;
while (time++ < times && BooleanUtils.isFalse(result)) {
result = compareAndAddAge(id, value);
}
return result;
} private static Boolean compareAndAddAge(long id, int value) {
try (SqlSession session = openSession(getDataSource1())) {
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.getUser(id);
Boolean result = userMapper.compareAndSetAgeById(id, user.getVersion(), user.getAge() + value);
session.commit();
return result;
}
} public static void main(String[] args) throws Exception {
long id = 1L;
int threads = 50;
CountDownLatch countDownLatch = new CountDownLatch(threads);
log.info("user:{}", getUser(id));
long start = System.currentTimeMillis();
exe(countDownLatch, threads, () -> addAge(1, 1, 20));
countDownLatch.await();
long end = System.currentTimeMillis();
log.info("end - start : {}", end - start);
log.info("user:{}", getUser(id));
}
758  [main] INFO  cn.eagle.li.mybatis.UpdateMain - user:User(age=0, name=3, id=1, version=0)
1270 [main] INFO cn.eagle.li.mybatis.UpdateMain - end - start : 509
1277 [main] INFO cn.eagle.li.mybatis.UpdateMain - user:User(age=50, name=3, id=1, version=50)

从输出可以看出,并发的情况下,结果是没问题的。

悲观锁

private static Boolean addAgeForUpdate(long id, int value) {
Boolean result;
try (SqlSession session = openSession(getDataSource1())) {
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.getUserForUpdate(id);
result = userMapper.setAgeById(id, user.getAge() + value);
session.commit();
}
return result;
} public static void main(String[] args) throws Exception {
long id = 1L;
int threads = 50;
CountDownLatch countDownLatch = new CountDownLatch(threads);
log.info("user:{}", getUser(id));
long start = System.currentTimeMillis();
exe(countDownLatch, threads, () -> addAgeForUpdate(1, 1));
countDownLatch.await();
long end = System.currentTimeMillis();
log.info("end - start : {}", end - start);
log.info("user:{}", getUser(id));
}
631  [main] INFO  cn.eagle.li.mybatis.UpdateMain - user:User(age=0, name=3, id=1, version=50)
829 [main] INFO cn.eagle.li.mybatis.UpdateMain - end - start : 196
837 [main] INFO cn.eagle.li.mybatis.UpdateMain - user:User(age=50, name=3, id=1, version=50)

从输出可以看出,并发的情况下,结果是没问题的。

总结

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

利用MySQL中的乐观锁和悲观锁实现分布式锁的更多相关文章

  1. mysql中的乐观锁和悲观锁

    mysql中的乐观锁和悲观锁的简介以及如何简单运用. 关于mysql中的乐观锁和悲观锁面试的时候被问到的概率还是比较大的. mysql的悲观锁: 其实理解起来非常简单,当数据被外界修改持保守态度,包括 ...

  2. 图解Janusgraph系列-并发安全:锁机制(本地锁+分布式锁)分析

    图解Janusgraph系列-并发安全:锁机制(本地锁+分布式锁)分析 大家好,我是洋仔,JanusGraph图解系列文章,实时更新~ 图数据库文章总目录: 整理所有图相关文章,请移步(超链):图数据 ...

  3. SpringBoot--防止重复提交(锁机制---本地锁、分布式锁)

    防止重复提交,主要是使用锁的形式来处理,如果是单机部署,可以使用本地缓存锁(Guava)即可,如果是分布式部署,则需要使用分布式锁(可以使用zk分布式锁或者redis分布式锁),本文的分布式锁以red ...

  4. Spring Boot 2实现分布式锁——这才是实现分布式锁的正确姿势!

    参考资料 网址 Spring Boot 2实现分布式锁--这才是实现分布式锁的正确姿势! http://www.spring4all.com/article/6892

  5. 老司机带大家领略MySQL中的乐观锁和悲观锁

    原文地址:https://cloud.tencent.com/developer/news/227982 为什么需要锁 在并发环境下,如果多个客户端访问同一条数据,此时就会产生数据不一致的问题,如何解 ...

  6. MYSQL中的乐观锁实现(MVCC)简析

    https://segmentfault.com/a/1190000009374567#articleHeader2 什么是MVCC MVCC即Multi-Version Concurrency Co ...

  7. 分布式锁(一) Zookeeper分布式锁

    什么是Zookeeper? Zookeeper(业界简称zk)是一种提供配置管理.分布式协同以及命名的中心化服务,这些提供的功能都是分布式系统中非常底层且必不可少的基本功能,但是如果自己实现这些功能而 ...

  8. 分布式锁之三:Redlock实现分布式锁

    之前写过一篇文章<如何在springcloud分布式系统中实现分布式锁?>,由于自己仅仅是阅读了相关的书籍,和查阅了相关的资料,就认为那样的是可行的.那篇文章实现的大概思路是用setNx命 ...

  9. 【面试普通人VS高手系列】请说一下你对分布式锁的理解,以及分布式锁的实现

    一个工作了7年的Java程序员,私信我关于分布式锁的问题. 一上来就两个灵魂拷问: Redis锁超时怎么办? Redis主从切换导致锁失效怎么办? 我说,别着急,这些都是小问题. 那么,关于" ...

随机推荐

  1. IO ——字节流

    什么是流? 概念:内存与存储设备之间传输数据的通道.程序运行后保存在内存,文件一般在硬盘中,在程序中读写文件,需要在内存和存储设备中建立通道.数据借助流传输 流的分类: 按流向: 输入流:将存储设备中 ...

  2. 获取iframe的window对象

    在父窗口中获取iframe中的元素 // JS // 方法1: var iframeWindow = window.frames["iframe的name或id"]; iframe ...

  3. jdk1.8中hashmap的扩容resize

    当hashmap第一次插入元素.元素个数达到容量阀值threshold时,都会扩容resize(),源码: (假设hashmap扩容前的node数组为旧横向node数组,扩容后的node数组为新横向n ...

  4. python学习-Day22

    目录 今日内容详细 hashlib加密模块 什么是加密 加密算法 加密的使用 基本使用 指定算法(md5) 将明文数据传递给算法对象 获取加密之后的密文数据 加密补充 加盐处理 动态加盐 加密应用场景 ...

  5. XCTF练习题---CRYPTO---wtc_rsa_bbq

    XCTF练习题---CRYPTO---wtc_rsa_bbq flag:flag{how_d0_you_7urn_this_0n?} 解题步骤: 1.观察题目,下载附件 2.下载后是一个文件,不清楚格 ...

  6. 同时将代码备份到Gitee和GitHub

    同时将代码备份到Gitee和GitHub 如何将GitHub项目一步导入Gitee 如何保持Gitee和GitHub同步更新 如何将GitHub项目一步导入Gitee 方法一: 登陆 Gitee 账号 ...

  7. Visual Studio 修改NuGet 包缓存路径

    Visual Studio 下载的NuGet包默认会缓存到 C:\Users{Windows用户名}.nuget\packages 下,时间一长就会导致 C盘空间严重不足. 那么怎样去设置,让包缓存文 ...

  8. Git (常用命令)

    某程序猿退休后决定练习书法,于是花重金买下文房四宝.某日,饭后突生雅兴,一番磨墨拟纸 并点上上好檀香.定神片刻,泼墨挥毫,郑重地写下一行:Hello World 斯~ 有被冷到吗哈哈哈 Git常用命令 ...

  9. Linux 或 Windows 上实现端口映射

    点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! 通常服务器会有许多块网卡,因此也可能会连接到不同的网络, ...

  10. 终于有人把云计算、大数据和 AI 讲明白了【深度好文】

    一个执着于技术的公众号 我今天要讲这三个话题,一个是云计算,一个大数据,一个人工智能,我为什么要讲这三个东西呢?因为这三个东西现在非常非常的火,它们之间好像互相有关系,一般谈云计算的时候也会提到大数据 ...