目录
锁的定义
锁的实现
JVM中的锁
synchronized
ReentrantLock
Mysql 锁
共享锁(S) 与排它锁(X)
作用范围
意向锁
记录锁
间隙锁
Next-Key Locks
Insert Intention Locks
AUTO-INC Locks
常见锁使用的场景和用法
double check
避免死锁
顺带谈谈并发场景下常见的问题
读写混乱
Redis或者一些并发操作释放锁或者资源,没有检查是否是当前线程持有
首页 数据库 mysql教程 Java与Mysql锁相关知识点有哪些

Java与Mysql锁相关知识点有哪些

May 27, 2023 am 10:18 AM
mysql java

    锁的定义

    在计算机程序中锁用于独占资源,获取到锁才可以操作对应的资源。

    锁的实现

    锁在计算机底层的实现,依赖于CPU提供的CAS指令(compare and swsp),对于一个内存地址,会比较原值以及尝试去修改的值,通过值是否修改成功,来表示是否强占到了这个锁。

    JVM中的锁

    jvm中,有2个常用的锁

    synchronized

    synchronized是java提供的关键字锁,可以锁对象,类,方法。
    在JDK1.6以后,对synchronized进行了优化,增加了偏向锁和轻量锁模式,现在synchronized锁的运行逻辑如下:

    • 在初始加锁时,会增加偏向锁,即“偏向上一次获取该锁的线程”,在偏向锁下,会直接CAS获取该锁。该模式大大提高了单线程反复获取同一个锁的吞吐情况,在Java官方看来,大部分锁的争抢都发生在同个线程上。

    • 如果偏向锁CAS获取失败,说明当前线程与偏向锁偏向的线程不同,偏向锁就会升级成轻量锁,轻量锁的特点就是通过自旋CAS去获取锁。

    • 如果自旋获取失败,那么锁就会升级成重量锁,所有等待锁的线程将被JVM挂起,在锁释放后,再由JVM统一通知唤醒,再去尝试CAS锁,如果失败,继续挂起。

    很显然,偏向锁设计的目的是“在Java官方看来,对同一个锁的争抢大部分都发生在同个线程上”。
    轻量锁设计的目的是“在短期内,锁的争抢通过自旋CAS就可以获取到,短时间内的CPU自旋消耗小于线程挂起再唤醒的消耗”。
    重量锁就是最初优化前的synchronized的逻辑了。

    ReentrantLock

    说到ReentrantLock,就不得不说到JUC里的AQS了。
    AQS全称AbstractQueueSynchronizer,几乎JUC里所有的工具类,都依赖AQS实现。
    AQS在java里,是一个抽象类,但是本质上是一种思路在java中的实现而已。
    AQS的实现逻辑如下:

    • 构造一个队列

    • 队列中维护需要等待锁的线程

    • 头结点永远是持有锁(或持有资源)的节点,等待的节点在头结点之后依次连接。

    • 头结点释放锁后,会按照顺序去唤醒那些等待的节点,然后那些节点会再次去尝试获取锁。

    在synchronized锁优化以后,AQS的本质与synchronized并没有太大不同,两者的性能也并没有太大差距了,所以AQS现在的特点是:

    • 是在java api层面实现的锁,所以可以实现各种并发工具类,操作也更加灵活

    • 因为提供了超时时间等机制,操作灵活,所以不易死锁。如果发生死锁,它将更难以排查,因为jstack将不会显示死锁标识。

    • 可以实现公平锁,而synchronized必定是非公平锁。

    • 因为是JavaApi层实现的锁,所以可以响应中断。

    到这里你会发现,其实ReentrantLock可以说是synchronized在JavaApi层的实现。

    Mysql 锁

    共享锁(S) 与排它锁(X)

    作用范围

    这两种锁都包括行级锁和表级锁。
    获取共享锁时,如果该数据被其他事务的排它锁锁住,则无法获取,需要等待排它锁释放。

    意向锁

    作用范围

    意向锁为表锁,在获取表锁之前,一定会检查意向锁。

    意图锁定协议如下:

    在事务获得表中某行的共享锁之前,它必须首先获得表上的 IS 锁或更强的锁。

    在事务获得表中行的排他锁之前,它必须首先获得表的 IX 锁。

    在获取任意表锁的共享锁或排它锁之前,一定会检查该表上的共享锁。

    表锁以及意向锁的互斥规则如下:
    X IX S IS
    X Conflict Conflict Conflict Conflict
    IX Conflict Compatible Conflict Compatible
    S Conflict Conflict Compatible Compatible
    IS Conflict Compatible Compatible Compatible

    意向锁的作用在于:在获取表锁时,可以通过意向锁来快速判断能否获取。

    因为获取行级锁时,会先获取对应的意向锁,这样另外的事务在获取表锁时就可以通过意向锁快速的判断,而不需要每行去扫描。

    特别注意的是,意向锁是可以叠加的,即会存在多个,如T1事务获取了意向锁IX1和行级锁X1,T2事务依旧可以获取意向锁IX2和行级锁X2,所以仅在获取表级锁之前,才会检查意向锁。

    记录锁

    记录锁生效在索引上,用以在SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE时保护该行数据不被其他事务更改。

    记录锁在没有索引时依旧会生效,因为innodb会为每张表创建一个隐藏的索引。

    记录锁是最基本的行锁。

    间隙锁

    间隙锁生效在索引上,用于锁定索引值后的行,防止插入,在select from table where index=? for update时会生效,例如index=1,则会锁住index=1索引节点相关的行,防止其他事务插入数据

    但是并不会防止update语句,哪怕update的数据不存在。

    Next-Key Locks

    这个锁是记录锁和间隙锁的组合,简而言之在select from table where index=? for update时,既会有间隙锁防止insert,也会有记录锁在index上防止这一条数据的update和delete。这个Next-key只是对这两种锁的一种概括,因为这两种锁在select for update时通常会一起出现。

    Insert Intention Locks

    插入意向锁,和意向锁类似。不过是特殊的间隙锁,并不发生在select for update,而是在同时发生insert时产生,例如在两个事务同时insert索引区间为[4,7]时,同时获得该区间的意向锁,此时事务不会阻塞,例如A:insert-5,B:insert-7,此时不会阻塞两个事务。

    插入意向锁是一个特殊的间隙锁,是为了防止正常间隙锁锁区间的情况下,insert频繁阻塞而设计的,例如A:insert-5,B:insert-7,如果没有插入意向锁,那么5和7都要去尝试获取间隙锁,此时第二个事务就会被阻塞,但是通过插入意向锁,第二个事务就不会被阻塞,只有到插入的行确实冲突,才会被阻塞。

    AUTO-INC Locks

    自增锁,这个锁很明显是表级insert锁,为了保证自增主键的表的主键保持原子自增。

    对于锁这个东西,大家应该多去理解各种锁设计运行的原理和模型,这样在加深理解后,在使用起来才会更加深入和透彻。

    常见锁使用的场景和用法

    double check

    众所周知,mysql的事务对防止重复插入并没有什么卵用,唯一索引又存在很多缺点,业务上最好不要使用,所以一般来说防止重复插入的通用做法就是使用分布式锁,这就有一种比较常用的写法。

    final WeekendNoticeReadCountDO weekendNoticeReadCountDO = weekendNoticeReadRepositoryService.selectByNoticeId(noticeRequestDTO.getNoticeId());
    if (weekendNoticeReadCountDO == null) {
        final String lockKey = RedisConstant.LOCK_WEEKEND_READ_COUNT_INSERT + ":" + noticeRequestDTO.getNoticeId();
        ClusterLock lock = clusterLockFactory.getClusterLockRedis(
            RedisConstant.REDIS_KEY_PREFIX,
            lockKey
        );
        if (lock.acquire(RedisConstant.REDIS_LOCK_DEFAULT_TIMEOUT)) {
            //double check
            final WeekendNoticeReadCountDO weekendNoticeReadCountDO = weekendNoticeReadRepositoryService.selectByNoticeId(noticeRequestDTO.getNoticeId());
            if (weekendNoticeReadCountDO == null) {
                try {
                    lock.execute(() -> {
                        WeekendNoticeReadCountDO readCountDO = new WeekendNoticeReadCountDO();
                        readCountDO.setNoticeId(noticeRequestDTO.getNoticeId());
                        readCountDO.setReadCount(1L);
                        readCountDO.setCreateTime(new Date());
                        readCountDO.setUpdateTime(new Date());
                        weekendNoticeReadRepositoryService.insert(readCountDO);
                        return true;
                    });
                } catch (ApiException err) {
                    throw err;
                } catch (Exception e) {
                    log.error("插入", e);
                    throw new ApiException(ErrorEnum.SERVER_ERROR.getCode(), "服务端出错");
                }
            } else {
                weekendNoticeReadRepositoryService.noticeCountAdd(weekendNoticeReadCountDO);
            }
        } else {
            log.warn("redis锁获取超时,key:{}", lockKey);
            throw new ApiException(ErrorEnum.SERVER_ERROR.getCode(), "服务器繁忙,请稍后重试");
        }
    }
    登录后复制

    在获取到锁之后,可能是经过等待才获取到的锁,此时上一个释放锁的线程可能已经插入了数据了,所以在锁内部,依旧要再次校验一下数据是否存在。
    这种写法适合大多数需要唯一性的写场景。

    避免死锁

    如何避免死锁?最简单有效的方法就是:**不要在锁里再去获取锁,简而言之就是锁最好单独使用,不要套娃。
    也要注意一些隐性锁,比如数据库。
    事务A:

    • 插入[5,7],插入意向锁。

    • select for update更新[100,150],间隙锁。
      事务B:

    • select for update更新[90,120],间隙锁。

    • 插入[4,6],插入意向锁。

    此时在并发场景下,就可能会出现A持有了[5,7]的间隙锁,在等待事务B[90,120]的间隙锁,事务B也一样,就死锁了。
    **

    顺带谈谈并发场景下常见的问题

    读写混乱

    在写业务代码,定义一些工具类或者缓存类的时候,很容易疏忽而发生类似的问题。
    比如构建一个static缓存,没有使用ConcurrentHashMap中的putIfAbsent等方法,也没有加锁去构建,导致上面的线程刚put了,下面的线程就删掉了,或者重复构建2次缓存。

    Redis或者一些并发操作释放锁或者资源,没有检查是否是当前线程持有

    这点在Redis锁的示例代码也讲到了。
    线程A获取到锁,此时B,C在等待,然后A执行时间过长,导致锁超时被自动释放了,此时B获取到了锁,在快乐的执行,然后A执行完了之后,释放锁时没有判断是否还是自己持有,导致B持有的锁被删除了,此时C又获取到了锁,BC同时在执行。

    以上是Java与Mysql锁相关知识点有哪些的详细内容。更多信息请关注PHP中文网其他相关文章!

    本站声明
    本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

    热AI工具

    Undresser.AI Undress

    Undresser.AI Undress

    人工智能驱动的应用程序,用于创建逼真的裸体照片

    AI Clothes Remover

    AI Clothes Remover

    用于从照片中去除衣服的在线人工智能工具。

    Undress AI Tool

    Undress AI Tool

    免费脱衣服图片

    Clothoff.io

    Clothoff.io

    AI脱衣机

    Video Face Swap

    Video Face Swap

    使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

    热门文章

    热工具

    记事本++7.3.1

    记事本++7.3.1

    好用且免费的代码编辑器

    SublimeText3汉化版

    SublimeText3汉化版

    中文版,非常好用

    禅工作室 13.0.1

    禅工作室 13.0.1

    功能强大的PHP集成开发环境

    Dreamweaver CS6

    Dreamweaver CS6

    视觉化网页开发工具

    SublimeText3 Mac版

    SublimeText3 Mac版

    神级代码编辑软件(SublimeText3)

    热门话题

    Java教程
    1677
    14
    CakePHP 教程
    1431
    52
    Laravel 教程
    1334
    25
    PHP教程
    1280
    29
    C# 教程
    1257
    24
    为Web开发者准备的10个最新工具 为Web开发者准备的10个最新工具 May 07, 2025 pm 04:48 PM

    Web开发设计是一个充满潜力的职业领域。然而,这个行业也面临着诸多挑战。随着越来越多的企业和品牌转向网络市场,Web开发者有机会展示他们的技能并在职业生涯中取得成功。然而,随着对Web开发需求的持续增长,开发人员的数量也在增加,导致竞争日益激烈。但令人振奋的是,如果你具备天赋和意愿,你总能找到新方法来创造独特的设计和创意。作为一名Web开发人员,你可能需要不断寻找新的工具和资源。这些新工具和资源不仅能让你的工作更加便捷,还能提升工作质量,从而帮助你赢得更多的业务和客户。Web开发的趋势不断变化,

    MySQL与Oracle:许可,功能和福利 MySQL与Oracle:许可,功能和福利 May 08, 2025 am 12:05 AM

    MySQL和Oracle的主要区别在于许可证、功能和优势。1.许可证:MySQL提供GPL许可证,免费使用,Oracle采用专有许可证,价格昂贵。2.功能:MySQL功能简单,适合Web应用和中小型企业,Oracle功能强大,适合大规模数据和复杂业务。3.优势:MySQL开源免费,适合初创公司,Oracle性能可靠,适合大型企业。

    MySQL:SQL的实际应用 MySQL:SQL的实际应用 May 08, 2025 am 12:12 AM

    MySQL受欢迎的原因是其性能卓越且易于使用和维护。1.创建数据库和表:使用CREATEDATABASE和CREATETABLE命令。2.插入和查询数据:通过INSERTINTO和SELECT语句操作数据。3.优化查询:使用索引和EXPLAIN语句提升性能。

    phpmyadmin的功能:与MySQL(SQL)互动 phpmyadmin的功能:与MySQL(SQL)互动 May 07, 2025 am 12:16 AM

    phpMyAdmin通过Web界面简化MySQL数据库管理。1)创建数据库和表:使用图形界面轻松操作。2)执行复杂查询:如JOIN查询,通过SQL编辑器实现。3)优化和最佳实践:包括SQL查询优化、索引管理和数据备份。

    Java平台独立性:Web应用程序的优势 Java平台独立性:Web应用程序的优势 May 09, 2025 am 12:08 AM

    Java'splatformindependencebenefitswebapplicationsbyallowingcodetorunonanysystemwithaJVM,simplifyingdeploymentandscaling.Itenables:1)easydeploymentacrossdifferentservers,2)seamlessscalingacrosscloudplatforms,and3)consistentdevelopmenttodeploymentproce

    GO:使用标准'字符串”包的字符串操纵 GO:使用标准'字符串”包的字符串操纵 May 09, 2025 am 12:07 AM

    Go语言使用"strings"包进行字符串操作。1)拼接字符串使用strings.Join函数。2)查找子串使用strings.Contains函数。3)替换字符串使用strings.Replace函数,这些函数高效且易用,适用于各种字符串处理任务。

    关于Java平台独立性的真相:真的那么简单吗? 关于Java平台独立性的真相:真的那么简单吗? May 09, 2025 am 12:10 AM

    Java'splatFormIndenceIsnotsimple; itinvolvesComplexities.1)jvmCompatiblemustbeiblemustbeensurecensuredAcrospPlatForms.2)nativelibrariesandsycallsneedcarefulhandling.3)

    比较SQL和MySQL:语法和功能 比较SQL和MySQL:语法和功能 May 07, 2025 am 12:11 AM

    SQL和MySQL的区别与联系如下:1.SQL是标准语言,用于管理关系数据库,MySQL是基于SQL的数据库管理系统。2.SQL提供基本CRUD操作,MySQL在此基础上增加了存储过程、触发器等功能。3.SQL语法标准化,MySQL在某些地方有改进,如LIMIT用于限制返回行数。4.使用示例中,SQL和MySQL的查询语法略有不同,MySQL的JOIN和GROUPBY更直观。5.常见错误包括语法错误和性能问题,MySQL的EXPLAIN命令可用于调试和优化查询。

    See all articles