锁:主流锁整体认知(悲观锁与乐观锁、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)流程

锁:主流锁整体认知(悲观锁与乐观锁、CAS算法、自旋锁、synchronized分析)_第1张图片

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

(3)CAS存在的问题

ABA问题:

锁:主流锁整体认知(悲观锁与乐观锁、CAS算法、自旋锁、synchronized分析)_第2张图片

 

 

  • 线程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)源码

锁:主流锁整体认知(悲观锁与乐观锁、CAS算法、自旋锁、synchronized分析)_第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(监视器锁)

锁:主流锁整体认知(悲观锁与乐观锁、CAS算法、自旋锁、synchronized分析)_第4张图片

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

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

 锁:主流锁整体认知(悲观锁与乐观锁、CAS算法、自旋锁、synchronized分析)_第5张图片

 

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

 

你可能感兴趣的:(锁:主流锁整体认知(悲观锁与乐观锁、CAS算法、自旋锁、synchronized分析))