【温故知新】-java开发中涉及的各种锁盘点

java开发中会碰到各种锁,这篇针对java开发中会碰到的锁进行归纳总结

目录

1.概念

1.1乐观锁,悲观锁

1.2共享锁,排他锁

1.3活锁,死锁,饿锁

1.4分布式锁

2.相关问题

2.1synchronized

2.2ReentrantLock,StampedLock

2.3CAS及ABA问题

2.4synchronized的Double-check

2.5mysql中的行锁,表锁,间隙锁以及如何避免死锁

2.6分布式锁的几种实现方式总结


1.概念

1.1乐观锁,悲观锁

乐观锁:比较乐观,认为存在并发的可能性比较小,所以采取乐观的态度加锁,在数据提交更新的时候才会去校验是否发生冲突,比如常见的CAS锁.

悲观锁:比较悲观,认为并发存在且发生数据冲突的可能性较大,所以在整个数 据处理过程中都加锁,其它线程在处理数据的时候需要阻塞,挂起.

在并发和资源竞争不激烈的情况下,乐观锁的效率要比悲观锁高,在并发激烈的情况下,悲观锁更适合.

1.2共享锁,排他锁

悲观锁又可分为共享锁和排他锁.

共享锁:也称为读锁,多个线程可以共享,对该数据读取,但不能修改

排他锁:也称为写锁,如果一个线程获得了写锁,其它线程将不能获取写锁,只能等待其释放后才能竞争该锁

1.3活锁,死锁,饿锁

活锁:任务没有被阻塞,但由于某些条件不满足,一直处于尝试的状态,理论上存在自行解开的可能,但死锁不会自动解开.

死锁:两个及两个以上线程在资源竞争的过程中,出现互相等待的情况,若无外力干扰,将一直等待下去. 死锁是开发中需要避免的,否则可能会导致系统不可用.

饿锁:饿锁是活锁的一种,比如在非公平锁且线程之间竞争激烈的情况下,有可能会出现一个线程一直在尝试获取锁,但一直没有获取到锁,处于饥饿状态,这种情况就称之为饿锁

1.4分布式锁

分布式锁是在涉及分布式应用的场景下,为防止分布式应用之间的资源竞争相互干扰而设计的锁,在分布式应用中常被用到.

2.相关问题

2.1synchronized

synchronized是非公平的悲观锁,锁的释放由Jvm控制,在java中用的比较多,在早期的Jdk版本中都是以重量级锁的形式出现,性能比较差,在Jdk1.6以后,经过优化,性能好了很多,推荐使用.

jdk1.6以后synchronized锁在没有竞争时,状态为偏向锁(只是贴了个锁的标签而已,实际上根本就不是锁),当有线程竞争时,锁升级为轻量级锁(cas自旋锁,jvm底层汇编指令cmpxchg),当有一半以上的线程自旋时,说明竞争激烈,此时锁升级为重量级锁.

synchronized可以修饰类,对象,方法,代码块

2.2ReentrantLock,StampedLock

ReentantLock是jdk提供的公平/非公平悲观锁,加锁及解锁均需要显式代码控制,在一些特殊场景下,需要指定等待/唤醒某个线程,可以采用ReentrantLock的condition/signal来实现,但synchronized结合Object的wait/notify,notifyAll方法不能指定线程.

StampedLock是Jdk1.8提供的锁,因为它在读的过程中也允许写锁后写入,所以性能有较大提升,因为在大部分场景下,并发并没有那么高,读锁和写锁不需要互斥,StampedLock采取的是乐观读锁+悲观读锁相结合的形式,当乐观读的时候发现数据有改变,才会采用悲观读锁重新读取数据,所以性能有提高,但代价就是代码复杂度显著增加,而且StampedLock的源码非常复杂难读,所以在实际开发中其实截至目前为止鲜有接触,了解也仅是面试之需.

2.3CAS及ABA问题

CAS全称compare and swap 意为比较并交换,是典型的乐观锁,在java中很多锁及原子操作都是借助cas实现的,比如常用的AtomicInteger,底层就是借助Unsafe类提供的CAS操作实现的,假设当前值为A,期望旧值为A,此时有线程想将当前值由A修改为B,在改之前与期望旧值对比,若当前值等于期望旧值,就将当前值改为B,否则认为存在冲突,不修改. 通常情况下,这样做可以防止多个线程修改同一份数据产生冲突,但无法避免ABA问题,也就是说,当线程1在判断当前值A是否为期望旧值A时,此时有线程2将值改为了B,同时又有线程3将值改回了A,那么在线程1看来,当前值仍然是A,与期望旧值一致,于是线程1修改数据成功了,但实际上,线程1的当前值A已经不是修改之前的那个A了,已经被其它线程提交过修改了,只是它不知道而已,有点类似被偷换.

为了避免ABA问题,通常会引入版本号的概念,这点在mysql中会经常被用到,就是增加一个version字段,每当有线程修改该值,version字段就递增,在修改前判断version的值即可避免数据被其它线程篡改.

2.4synchronized的Double-check

在单例设计模式的懒汉式实现中,会用到double-check,至于为啥要double-cheke? 一方面是基于性能的考虑,比如第一层check,先预判有没有必要加锁,第二层的再次判断则是为了保障只有一个线程创建实例,是基于线程安全的考虑.

        if (obj == null){
            synchronized (this){
                if (obj == null){
                    obj = new Object();
                }
            }
        }

 

2.5mysql中的行锁,表锁,间隙锁以及如何避免死锁

行锁:Innodb引擎支持的行级锁,可以针对一行/多行数据进行加锁,锁的粒度是控制到行的.

表锁:Myisiam引擎支持,锁的粒度是控制到表级别的,性能比较差.

间隙锁:针对索引某一区间的数据进行加锁,加锁原则是左开右闭区间,解决可重复读.

避免死锁一是要控制加锁和解锁的顺序,同时尽量避免使用mysql级别的锁,多用jdk提供的锁及分布式锁.

2.6分布式锁的几种实现方式总结

分布式锁常见的实现方式有三类,一类是基于nosql 比如redis/memache实现的,这类分布式锁用的最多,性能比较高,实现简单.基于redis的分布式锁实现在redis篇有详细梳理,这里不再赘述.

一类是基于zookeeper实现的,这种实现方式可靠性高,但性能偏低,实现难度稍大,不过apache cretor已经有一套现成的实现方案可以直接拿来用.

还有就是几乎没人用的,基于msql实现的,主要适合入门和了解分布式锁.

基于这三类实现方式性能排序:redis>zk>mysql;实现复杂度排序:mysql<redis<zk

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值