前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java锁、悲观乐观锁、分布式锁?细说那年我们用过的锁

Java锁、悲观乐观锁、分布式锁?细说那年我们用过的锁

作者头像
品茗IT
发布2020-05-28 16:08:24
9070
发布2020-05-28 16:08:24
举报
文章被收录于专栏:品茗IT品茗IT

Java锁、悲观乐观锁、分布式锁?细说那年我们用过的锁

一、概述

Java锁,指的是应用中使用的锁;应用中在处理线程安全的问题时,常常使用synchronized 或者ReentrantLock等锁来保证线程安全。

悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。一般是指数据库中的行锁;

乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于读多写少的应用场景,这样可以提高吞吐量。一般是指数据库中携带version字段进行更新;

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。

**如果大家正在寻找一个java的学习环境,或者在开发中遇到困难,可以<a

href="https://jq.qq.com/?_wv=1027&k=52sgH1J"

target="_blank">

加入我们的java学习圈,点击即可加入

</a>

,共同学习,节约学习时间,减少很多在学习中遇到的难题。**

二、Java锁

Java锁还有很多种,但只能在应用内使用,在多机部署的应用间就无法保证数据的一致性。

根据锁的设计,可以分为下面这些(并不是一种设计就对应一种实现,比如ConcurrentHashMap,其并发的实现就是通过分段锁的形式,但它并不是锁):

  • 公平锁/非公平锁
  • 可重入锁
  • 独享锁/共享锁
  • 互斥锁/读写锁
  • 乐观锁/悲观锁
  • 分段锁
  • 偏向锁/轻量级锁/重量级锁
  • 自旋锁

我这里简单使用synchronized 或者ReentrantLock来介绍下JAVA锁。

2.1 synchronized

Java锁中最简单的一种锁,最常见的就是拿它来做线程安全的工厂方法。只需要在方法上加上synchronized即可保证应用内线程安全。

代码语言:javascript
复制
// 单例对象
private static SingletonExample instance = null;

// 静态的工厂方法
public static synchronized SingletonExample getInstance() {
  if (instance == null) {
    instance = new SingletonExample();
  }
  return instance;
}

synchronized准确来说也是悲观锁的一种,因为加了synchronized的方法在应用内同一时间只允许一个线程操作,其他线程将会被阻塞。

2.2 ReentrantLock

ReentrantLock,顾名思义,它是支持可重入锁的锁,是一种递归无阻塞的同步机制。除此之外,该锁还支持获取锁时的公平和非公平选择。ReentrantLock默认构造函数是NonfairSync(非公平锁)。

代码语言:javascript
复制
private ReentrantLock lock = new ReentrantLock();

public void print(int str) {
  try {
    lock.lock();
    System.out.println(str + "获得");
    Thread.sleep((int) (Math.random() * 1000));
  } catch (Exception e) {
    e.printStackTrace();
  } finally {
    System.out.println(str + "释放");
    lock.unlock();
  }
}

公平锁就算,在绝对时间上,先对锁进行获取的请求你一定先被满足,那么这个锁是公平的,反之,是不公平的。

三、悲观锁

上面说到的那两个Java锁,广义上也是悲观锁;然而,现在大多数人提到悲观锁和乐观锁,常常都是指数据库操作过程中的数据一致性处理方式。

下面对Mybatis和Spring-Data-Jpa的悲观锁写法做简单说明。

3.1 Mybatis

Mybatis是需要自己写Sql的,因此,我们只需要在需要使用悲观锁的地方,比如要对某一条数据做更新,可以先select 当前语句,并加上for update,此时这条数据就被加锁。

代码语言:javascript
复制
@Select({
  "<script>",
    "SELECT ",
    "user_name as userName,passwd,name,mobile,valid, user_type as userType, version as version",
    "FROM user_info_test",
    "WHERE user_name = #{userName,jdbcType=VARCHAR} for update",
   "</script>"})
UserInfo findByUserNameForUpdate(@Param("userName") String userName);

Mysql的MyISAM引擎不支持行锁,它的for update是表锁。

参考这篇文章:SpringBoot入门建站全系列(二十一)Mybatis使用乐观锁与悲观锁

3.2 Spring-Data-Jpa

Spring-Data-Jpa可以不手写SQL,当然你也可以手写SQL。要用悲观锁的话,你手写SQL也是没用的。必须使用@Lock注解才能实现悲观锁。

代码语言:javascript
复制
public interface UserInfoDao extends CrudRepository<UserInfo, String> {
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    UserInfo findByUserName(String userName);
}

参考这篇文章:SpringBoot入门建站全系列(二十)SpringDataJpa使用乐观锁与悲观锁

四、乐观锁

在Java锁中,也是有乐观锁的实现的,比如CAS操作,就是乐观锁,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。

但是,我们常说乐观锁,一般都是指数据库的乐观锁使用。下面对Mybatis和Spring-Data-Jpa的乐观锁写法做简单说明。

4.1 Mybatis

乐观锁是需要表中额外增加字段做配合的,一般是version字段。如:

代码语言:javascript
复制
@Update({
  "<script>",
  " update user_info_test set",
  " name = #{name, jdbcType=VARCHAR}, mobile = #{mobile, jdbcType=VARCHAR},version=version+1 ",
  " where user_name=#{userName} and version = #{version}",
  "</script>"
})
int updateWithVersion(UserInfo userInfo);

Mybatis对某条数据做更新时,需要带处理业务前查询出的version字段,如果数据库中version字段值和更新时携带的version值不同,则更新失败(注意:Mybatis更新失败不会报错,只是返回0)。

参考这篇文章:SpringBoot入门建站全系列(二十一)Mybatis使用乐观锁与悲观锁

4.2 Spring-Data-Jpa

Spring-Data-Jpa使用@Version注解来实现乐观锁,同时数据库表中要有version字段。如在实体上加上@Version注解:

代码语言:javascript
复制
@Version
private Integer version;

Spring-Data-Jpa对某条数据做更新时,如果实体的字段上有@Version注解,会检测version字段值和数据库是否一致,不一致,会抛出optimistic locking failed异常。这时需要自己捕获异常进行处理。

参考这篇文章:SpringBoot入门建站全系列(二十)SpringDataJpa使用乐观锁与悲观锁

五、分布式锁

数据库的悲观锁和乐观锁也能保证不同主机共享数据的一致性。但是却存在以下问题:

  • 悲观锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。
  • 一旦悲观锁解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁
  • 乐观锁适合读多写少的场景,如果在分布式场景下使用乐观锁,就会导致总是更新失败,效率极低。

下面对Redis和Zookeeper的分布式锁写法做简单说明。

5.1 Redis

Redis实现分布式锁,可以使用redisson来实现。示例:

代码语言:javascript
复制
@Autowired
protected RedissonClient redissonClient;

public ResultModel tryLock(LockWorker lockWorker) {
  try {
    RLock lock = redissonClient.getFairLock(LOCKER_PREFIX + LOCKER_GOODS_CONSUME);
    // (公平锁)最多等待10秒,锁定后经过lockTime秒后自动解锁
    boolean success = lock.tryLock(LOCKER_WAITE_TIME, LOCKER_LOCK_TIME, TimeUnit.SECONDS);
    if (success) {
      try {
        return lockWorker.invoke();
      } finally {
        lock.unlock();
      }
    }
    return ResultModel.error("获取分布式锁失败!");  
  } catch (Exception e) {
    return ResultModel.error("获取分布式锁过程异常!");
  }
}

示例中,当我们配置好redis之后,使用RedissonClient来获取一个公平锁(也可以是其他种类锁),设置超时时间和自动解锁时间。获取到锁之后,这个锁就是在集群环境下唯一的。当我们执行LockWorker的业务逻辑时,就能保证集群在同一时间只有一个线程在执行LockWorker的业务。

5.2 Zookeeper

spring-integration对redis和zookeeper都做了整合,但是上面我并没有使用spring-integration,这里我将使用spring-integration-zookeeper来实现基于zookeeper的分布式锁。示例:

代码语言:javascript
复制
@Autowired
protected ZookeeperLockRegistry zookeeperLockRegistry;

public ResultModel tryLock(LockWorker lockWorker) {
  try {
    if (useLock) {
      Lock lock = zookeeperLockRegistry.obtain(LOCKER_GOODS_CONSUME);
      // (公平锁)最多等待10秒,锁定后经过lockTime秒后自动解锁
      boolean success = lock.tryLock(10, TimeUnit.SECONDS);
      if (success) {
        try {
          return lockWorker.invoke();
        } finally {
          lock.unlock();
        }
      }
      return ResultModel.error("获取分布式锁失败!");
    } else {
      return lockWorker.invoke();
    }
  } catch (Exception e) {
    return ResultModel.error("获取分布式锁过程异常!");
  }
}

示例中,使用了ZookeeperLockRegistry,这个位于spring-integration-zookeeper中,需要我们@Bean声明。获取锁和执行业务的逻辑和上面的redis一模一样。一样能保证集群在同一时间只有一个线程在执行LockWorker的业务。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-09-11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Java锁、悲观乐观锁、分布式锁?细说那年我们用过的锁
  • 一、概述
  • 二、Java锁
    • 2.1 synchronized
      • 2.2 ReentrantLock
      • 三、悲观锁
        • 3.1 Mybatis
          • 3.2 Spring-Data-Jpa
          • 四、乐观锁
            • 4.1 Mybatis
              • 4.2 Spring-Data-Jpa
              • 五、分布式锁
                • 5.1 Redis
                  • 5.2 Zookeeper
                  相关产品与服务
                  数据库
                  云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档