• 深入理解锁


    目录

    常用锁策略

    1.乐观锁 VS 悲观锁

    2.轻量级锁 VS 重量级锁

    3.自旋锁 VS 挂起等待锁

    4.互斥锁 VS 读写锁

    5.公平锁 VS 非公平锁

    6.可重入锁 VS 可重入锁

    CAS

    ABA问题

    Synchronized原理

    1. 锁升级/锁膨胀

    2.锁消除

    3.锁粗化


    常用锁策略

    1.乐观锁 VS 悲观锁

    站在锁冲突概率的预测角度.乐观锁预测冲突概率较小,悲观锁预测锁冲突概率较大

    synchronized既是一个悲观锁,也是一个乐观锁.它默认是一个乐观锁,但当锁竞争比较激烈,就会变成悲观锁.

    2.轻量级锁 VS 重量级锁

    站在加锁操作的开销角度. 轻量级锁开销较小,重量级锁开销较大.

    synchronized默认是一个轻量级锁,但发现锁竞争比较激烈的时候就会转转换成重量级锁.

    3.自旋锁 VS 挂起等待锁

    自旋锁是一种典型的轻量级锁,对于自旋锁,当锁被释放后,线程能第一时间感知到锁,从而有机会获取到锁

    挂起等待锁是一种典型的重量级锁.当锁被释放后,继续等待,不知道什么时候能过获取到锁

    synchronized这里的轻量级锁是基于自旋锁的方式实现的,而synchronized的重量级锁是针对挂起等待锁的方式实现的.

    4.互斥锁 VS 读写锁

    互斥锁提供加锁和解锁两种操作,如果一个线程加锁,另外一个线程也尝试加锁,就会产生阻塞等待.

    读写锁提供了三种操作,分别是针对读操作加锁,针对写操作加锁和解锁操作.多线程针对同一个变量并发读,这个时候没有线程安全问题,也不需要加锁控制.

    读锁和读锁之间没有互斥,写锁和写锁之间存在互斥,写锁和读锁之间存在互斥.

     synchronized不是读写锁

    5.公平锁 VS 非公平锁

    所谓公平,就是指 " 先来后到 ",下面举个栗子

     公平锁: 当女神分手后,由等待队列中最早来的舔狗上

     非公平锁: 就是当女神分手后,三个滑稽老铁都有了追求女神的机会,而和之前追了多长时间没有关系.

    在操作系统和 Java synchronized 中都是非公平锁,操作系统针对加锁的控制,本身依赖线程的调度顺序,这个调度顺序是随机的,不会考虑线程等待了多长时间.

    6.可重入锁 VS 可重入锁

    不可重入锁: 一个线程针对一把锁,连续加锁两次出现死锁

    可重入锁: 一个线程针对一把锁,连续加锁多次都不会死锁.

    synchronized是可重入锁.

    关于可重入问题和synchronized的相关操作

    CAS

    CAS指的是 compare and swap指的是 比较并交换,一个CAS涉及到一下操作:

    上述这个CAS过程并非是通过异端代码实现的,而是通过一条 CPU指令完成的.而CAS操作是原子的,在一定程度上就回避了线程安全问题,同时在解决线程安全问题除了加锁之外,又可以使用CAS方法了.

    CAS相当于通过一个原子的操作 , 同时完成 " 读取内存 , 比 较是否相等, 修改内存 " 这三个步骤 .

    CAS可以实现原子类,在Java标准库中提供的类 

    接下来用原子类写一个两个线程并发自加的操作

    1. import java.util.concurrent.atomic.AtomicInteger;
    2. public static void main(String[] args) {
    3. //这些原子类,就是基于 CAS 实现了 自增,自减等操作
    4. //此时进行这类操作不用加锁也是线程安全的
    5. AtomicInteger count = new AtomicInteger(0);
    6. Thread t1 = new Thread(()-> {
    7. for (int i = 0; i < 50000; i++) {
    8. count.getAndIncrement(); //count++
    9. // count.incrementAndGet(); //++count
    10. // count.getAndDecrement(); //count--
    11. // count.decrementAndGet(); //count--
    12. }
    13. });
    14. Thread t2 = new Thread(()-> {
    15. for (int i = 0; i < 50000; i++) {
    16. count.getAndIncrement(); //count++
    17. }
    18. });
    19. t1.start();
    20. t2.start();
    21. try {
    22. t1.join();
    23. t2.join();
    24. } catch (InterruptedException e) {
    25. e.printStackTrace();
    26. }
    27. System.out.println(count.get());
    28. }

    上面操作用伪代码进行实现

    原子类这里的实现,每次修改之前,在确认一下这个值是否符合要求.

    CAS还可以实现自旋锁

    接下来看看CAS实现的自旋锁的伪代码

    注意: 在java中并没有提供CAS方法,此处CAS相当于是一个简化的方式

    ABA问题

    CAS在运行中的核心是检查value和oldValue是否一致,如果一致,就视为value中途没有被修改过,在进行下一步操作,但可能在检查value和oldValue是否一致之前,可能会出现value从A修改成B,又从B修改为A这样的操作,CAS无法判断这种操作是否发生,这样的问题就叫ABA问题

    针对这样的问题,采取的方案就是给要修改的数据加版本号,想象初始版本号是1,每次修改版本号都+1,人后进行CAS的时候,不是一以金额为基准了,而是以版本号为基准,因为版本号是只会自加的,而不会减少.

    Synchronized原理

    前面提到过,synchronized关键字,两个线程针对同一个线程加锁,就会产生阻塞等待.但在synchronized内部还有一些优化机制,存在的目的就是为了让锁更高效.

    1. 锁升级/锁膨胀

    通过Synchronized关键字进行加锁的的过程中,Synchronized会经历,无锁,偏向锁,轻量级锁,和重量级锁四种状态.

            第一个尝试加锁的线程,优先进入偏向锁的状态.

    偏向锁不是真的 " 加锁 ", 只是给对象做一个 " 偏向锁的标记 ", 记录这个锁属于哪个线程 .
    如果后续没有其他线程来竞争该锁 , 那么就不用进行其他同步操作了 ( 避免了加锁解锁的开销 )
    如果后续有其他线程来竞争该锁 ( 刚才已经在锁对象中记录了当前锁属于哪个线程了 , 很容易识别 当前申请锁的线程是不是之前记录的线程), 那就取消原来的偏向锁状态 , 进入一般的轻量级锁状态 .
    偏向锁本质上相当于 " 延迟加锁 " . 能不加锁就不加锁 , 尽量来避免不必要的加锁开销 .
    但是该做的标记还是得做的 , 否则无法区分何时需要真正加锁 .
            当synchronized发生锁竞争的时候就会从偏向锁升级成轻量级锁,此时,synchronized相当于通过自旋的方式,来进行加锁的.(和上面CAS讲的伪代码一样).
            如果竞争进一步加强,自旋锁不能获取到当前锁状态,就会进入重量级锁.重量级锁是操作系统内核原生的API实现的,此时这个加锁功能就会影响到 线程的调度.如果线程进入重量级加锁,并且发生锁竞争,此时线程会被放到阻塞队列里,暂时不参与调度,直到锁被释放,才有机会被调度,并重新获取到锁.

    2.锁消除

    编译器智能的判定,看当前的代码是否真的要加锁,如果这个场景不需要加锁,而我们加了,编译器聚会自动把锁干掉.

    3.锁粗化

    锁的粒度: synchronized 包含的代码越多,粒度就越粗,包含的代码越少,粒度就越细.

    通常情况下,认为锁的粒度细一点比较好,因为加锁部分的代码,是不能并发执行的,锁的粒度越细,能并发执行的代码就越多;反之,就越少.

    但也有特殊的情况,有时候可能没有现成来抢占这个锁,jvm就会自动把锁粗化,避免频繁申请释放锁.

  • 相关阅读:
    springboot项目:加入购物车
    Nacos配置中心集群原理及源码分析
    从权限系统的菜单管理看算法和数据结构
    V8是如何执行JavaScript代码的?
    Android Automotive编译
    B站短视频如何去水印?一键解析下载B站视频!
    水环保网关在湿地保护有什么作用?
    TDengine Restful Authorization 自定义Token
    欧盟《数据治理法》说明
    html截取最后几个字符
  • 原文地址:https://blog.csdn.net/2301_76692760/article/details/134405505