zoukankan      html  css  js  c++  java
  • 锁:主流锁整体认知(悲观锁与乐观锁、CAS算法、自旋锁、synchronized分析)

    1、悲观锁与乐观锁

    (1)悲观锁与乐观锁

    悲观锁

    • 悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,在获取数据的时候会先加锁,确保数据不会被别的线程修改
    • 锁实现:关键字synchronized、接口Lock的实现类
    • 适用场景:写操作较多,先加锁可以保证写操作时数据正确

    乐观锁

    • 乐观锁认为自已在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据
    • 锁实现: CAS算法, 例如AtomicInteger 类的原子自增是同过CAS自旋实现
    • 适用场景:读操作较多,不加锁的特点能够使其读操作的性能大幅提升

    (2)执行流程

    悲观锁

    多个线程尝试获取同步资源的锁(给同步资源加锁)

    某个线程加锁成功并执行操作,其他线程进入等待

    获取到锁的线程执行完成之后会释放锁,然后CPU唤醒等待的线程,被唤醒的线程再次尝试获取锁

    乐观锁

    线程直接获取同步资源数据执行各自的操作

    更新内存中的同步资源之前先判断资源是否被其他线程修改。没有被修改,就可以更新内存中同步资源的值;被其他线程修改的话就根据实现方法执行不同的操作

    2、CAS算法(Compare And Swap)

    (1)概念

    • 无锁算法: 基于硬件原语实现,在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步
    • Jdk中实现: java. util. concurrent包中的原子类(AtomicInteger)就是通过CAS来实现了乐观锁
    • 算法涉及到三个操作数:需要读写的内存值V ,进行比较的值A, 要写入的新值B

    (2)流程

    • 线程1上的A值是否等于主内存中的V值(CAS),相等的话线程1可以修改主内存中的值,此时主内存中的V值等于线程1的B值=1
    • 当线程2再次进行CAS的时候发现V=1≠A,更新失败后有两种选择:报错和自旋(重新load主内存V的值到线程栈),线程进行自旋后进行第二次的CAS。还有可能就是在同步V值之后进行CAS之前又有线程对内存中的数据进行了更改,再次进行CAS的时候还是会失败,此时还要进行第二次的自旋

    (3)CAS存在的问题

    ABA问题:

    • 线程1先进行CAS算法并更新成功,更新后V=1。然后,线程3进行自旋,进行CAS算法更新后V=0,也就是说经过两次更新操作后,V变回了初始值。如果线程2再次进行CAS是可以成功的,V的值已经更改过了,但是线程2并没有感知到V值的变化,这就是ABA问题
    • 可以为数据增加版本号,可以感知到状态的改变

    循环时间开销比较大

    只能保证一个共享变量的原子操作

    • AtomicReference类可以保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作

    3、自旋锁

    (1)概念

    是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,白旋直到获取到锁才会退出循理

    (2)自旋锁存在的意义与使用场景

    阻塞与唤醒线程需要操作系统切换CPU状态,需要消耗一 定时间

    同步代码块逻辑简单,执行时间很短

    自适应自旋假定不同线程持有同一个锁对象的时间基本相当,竞争程度趋于稳定,因此,可以根据上一次自旋的时间与结果调整下一次自旋的时间。例如:上次35次成功了,但是这一次35次了还没有成功,那么就会考虑多加一点次数。如果还没有成功,超出自旋的上限就会认为自旋成功的几率越来越低,后续的话会对其进行降级,减少自旋上限

    JDK1.6通过-XX:-UseSpinning参数关闭自旋锁优化;-XX:PreBlockSpin参 数修改默认的自旋次数。 JDK>=1.7自旋锁的参数被取消,虚拟机不再支持由用户配置自旋锁,自旋锁总是会执行,自旋锁次数也由虚拟机自动调整。

    (3)源码

    •  阻塞与唤醒(上下文切换)一个线程要花掉较多的资源,自旋可以减少资源的占用。
    • JVM如果要通知CPU进行上下文切换是不能直接向CPU发送指令的,要向操作系统发送消息,让操作系统的CPU从用户态切换到内核态,然后通知CPU进行上下文切换
    • 线程的上下文切换,执行一个时间片后如果切换下一个任务,就要保存当前任务的状态(这些线程的中间变量存储在内存中的PCB区块),以便下一次继续执行此任务,再次执行此线程的时候再将数据读取出来

    4、synchronized分析(https://fanyi.baidu.com/?aldtype=16047#en/zh/synchronized

    (1)使用方式

    同步实例方法,锁是当前实例对象

    同步类方法,锁是当前类对象

    同步代码块,锁是括号里面的对象

    (2)实现方式

    synchronized是JVM内置锁,通过内部对象Monitor(监视器锁)实现,基于进入与退出Monitor对象实现方法与代码块同步,监视器锁的实现依赖底层操作系统的Mutex lock (互斥锁)实现

    (3)每一个同步对象都有自己的Monitor(监视器锁)

    线程进入同步块,首先要判断当前线程对象是否拿到了监视器锁,拿到的话才能进入到同步块,没有拿到的话会进入同步队列排队,当其他线程释放锁以后再唤醒,进行下一轮尝试获取锁的操作

    (4)JVM内置锁的膨胀升级

     

     这个过程不能逆向,可以释放锁,然后从偏向锁状态开始升级

  • 相关阅读:
    抽象类存在的意义
    抽象类的特征
    抽象类的使用
    抽象类的概述
    引用类型作为方法参数和返回值
    继承的特点
    目前Java水平以及理解自我反思---01
    继承后- 构造器的特点
    指针函数
    C数组灵活多变的访问形式
  • 原文地址:https://www.cnblogs.com/zhai1997/p/13467606.html
Copyright © 2011-2022 走看看