理解Java中锁的应用

一、死锁

1.1 定义:

当线程 A 持有独占锁a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。

1.2 死锁产生的条件:

  • 互斥条件:进程对所分配到的资源具有排他性,即一个资源只能被一个进程占用,直到被该进程释放
  • 请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放
  • 不剥夺条件:任何一个资源在没有被该进程释放之前,对已获得的资源保持不放
  • 循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路,造成永久阻塞

1.3 如何防止死锁:

  • 尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。
  • 尽量使用 Java. util. concurrent 并发类代替自己手写锁。
  • 尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
  • 尽量减少同步的代码块。
  • 死锁检测:每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph)将其记下。每当线程请求锁,也需要记录在这个数据结构中。针对不能实现按序加锁并且锁超时不可行的场景

二、synchronized

  • 自旋锁:如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。
  • 偏向锁:它会偏向第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,减少加锁解锁的一些CAS操作,这种情况下就会给线程加一个偏向锁。如果在运行过程中遇到其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。
  • 轻量级锁:是由偏向锁升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用时,偏向锁会升级为轻量级锁。
  • 重量级锁:当进入一个同步、线程安全的方法时,需要先获取这个方法的锁,退出这个方法时则会释放锁。如果获取不到这个锁,意味着有别的线程在执行这个方法,这时我们就会马上进入阻塞状态,等待那个持有锁的线程释放锁,然后再把我们从阻塞状态唤醒,我们再去获取这个方法的锁。这种获取不到锁就马上进入阻塞状态的锁称为重量级锁。

三、ReentrantLock

3.1 synchronized 和 Lock的区别(如ReentrantLock)

  • synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
  • synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
  • 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
  • Lock是API级别的,synchronized是JVM级别的
  • Lock可实现公平锁
  • Lock可通过Condition绑定多个条件

3.2 特点

可重入性:若一个程序或子程序可以“安全的被并行执行(Parallel computing)”,则称其为可重入(reentrant或re-entrant)的。即当该子程序正在运行时,可以再次进入并执行它(并行执行时,个别的执行结果,都符合设计时的预期)。
线程可以重复获取同一把锁

四、CAS

JUC提供的CAS机制实现无锁的解决方案,以同步非阻塞的方式保证线程安全,保证在并发下共享线程的原子性操作,类似乐观锁
理解:逻辑计算前读取和计算后读取的值进行比较,以判断这个值有没有被修改,进而判断是否可以对其进行操作
在这里插入图片描述

4.1 AutomicInteger

AtomicInteger​是java.util.concurrent.atomic 包下的一个原子类
getAndXXX格式的方法都实现了原子操作

public final int getAndSet(int newValue):获取当前的值,并设置新的值

核心源码:
理解:获取int value的“起始地址”的偏移量,value使用volatile进行修饰(保证可见性和有序性),Unsafe类实现CAS

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
    static {
        try {
            // 用于获取value字段相对当前对象的“起始地址”的偏移量
            valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

    //返回当前值
    public final int get() {
        return value;
    }

    //递增加detla
    public final int getAndAdd(int delta) {
        // 1、this:当前的实例 
        // 2、valueOffset:value实例变量的偏移量 
        // 3、delta:当前value要加上的数(value+delta)。
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }

五、数据库锁

在这里插入图片描述
行锁:基于索引加载,锁一行或多行,锁定时其他事务无法访问。缺点:可能会造成死锁
表锁:全表扫描,锁定时其他事务不能对表进行访问
记录锁:锁表中的某一条数据,命中索引为唯一索引
间隙锁:锁定一个区间,查询条件命中索引,并且没有查询到符合条件的记录,此时就会将查询条件中的范围数据进行锁定(即使是范围库中不存在的数据也会被锁定)。只适用于可重复读的事务隔离级别
临界锁:查询条件命中索引,并且查询到符合条件的记录。临键锁锁定区间和查询范围后匹配值很重要,如果后匹配值存在,则只锁定查询区间,否则锁定查询区间和后匹配值与它的下一个值的区间。(原因:当我们的索引树上只有1、5、7时,我们查询1-8,这个时候由于树节点关键字中并没有8,所以就把8到正无穷的区间范围都给锁定了;如果我们数据库中id有1、5、7、10,此时我们再模糊匹配id为1~8的时候,由于关键字中并没有8,所以找比8大的,也就找到了10,根据左开右闭原则,此时10也是被锁定的,但是id为11的记录还是可以正常进行插入的。)

六、分布式锁

6.1 分布式锁的作用

  • 避免不同节点重复工作
  • 避免多节点同时操作同一资源,导致结果不一致

6.2 分布式锁的特点

  • 互斥性:和我们本地锁一样互斥性是最基本,但是分布式锁需要保证在不同节点的不同线程的互斥。
  • 可重入性:同一个节点上的同一个线程如果获取了锁之后那么也可以再次获取这个锁。
  • 锁超时:和本地锁一样支持锁超时,防止死锁。
    高效,高可用:加锁和解锁需要高效,同时也需要保证高可用防止分布式锁失效,可以增加降级。
  • 支持阻塞和非阻塞:和ReentrantLock一样支持lock和trylock以及tryLock(long timeOut)。
  • 支持公平锁和非公平锁(可选):公平锁的意思是按照请求加锁的顺序获得锁,非公平锁就相反是无序的。

6.3 常见的分布式锁

  • MySql
  • Zk
  • Redis
  • 自研分布式锁:如谷歌的Chubby

redis分布式锁:

// 设置过期时间,并对资源加锁
// Redis2.8之后支持nx和ex是原子操作,之前需要使用lua脚本
 set resourceName value ex 5 nx

Redisson:为Redis的客户端,整合了分布式锁功能。实现像操作本地锁一样操作Redis的分布式锁。
RedLock:大多数集群加锁成功。避免Redis宕机,一个节点获取锁后,第二个节点获取同一把锁的问题。
在这里插入图片描述

zookeeper分布式锁:

创建锁节点下的一系列临时顺序节点,形成排队机制,节点对前一个节点进行监听,前一个节点释放锁,则自己顺位获取锁。
优势:客户端创建临时顺序节点后,宕机,zookeeper会自动删除临时节点,自动释放锁,避免死锁。

  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值