Java锁优化
首先解释一下,Java中,对锁的优化,主要是优化 sychronized 关键字,优化是在JVM层面,并且是在JDK5之后,因为之前 sychronized 的运行效率太低
先看看编译之后的Class文件
在JVM中,每个对象都会有一个对象头的数据Mark Word,该对象头中存储了对象的哈希码,对象分代年龄,锁标识,还有偏向模式
锁状态 | 哈希码 | 分代年龄 | 偏向模式 | 标识位 | |
---|---|---|---|---|---|
未锁定 | 01 | ||||
轻量锁定 | 00 | ||||
重量锁定 | 10 | ||||
GC标识 | 01 | ||||
偏向 | 01 |
当使用 sychronized 的时候,首先就是将对象的未锁定状态改变为锁定状态,并且记录下获取锁的线程ID,因为 sychronized 是可重入的锁,所以还需记录获取锁的线程的重入次数
什么是偏向?
偏向的意思就是当没有线程竞争获取锁的时候,锁会偏向于第一个获取该锁的线程,在第一次获取锁之后,如果之后都没有其他线程想要获取锁,那么持有锁的线程以后都不需要同步,不需要同步会提高代码的执行效率,因为 sychorinzed 在获取锁的时候,是一个系统调用过程,系统调用过程就必须用自陷切换到和心态去执行,上下文切换带来的开销肯定很大,所以这是优化的第一步!
出现竞争,偏向失败,接下来怎么办?自旋,自旋优化
当出现多个线程竞争1个锁的时候,系统会放弃使用偏向锁,转而进行第二步的优化,使用自旋锁,所谓自旋锁,顾名思义,就是使用一个循环,不断的去尝试获取锁,从而避免获取不到锁的线程被阻塞,因为被阻塞会从用户态切换到和心态,有上下文切换的开销,这是一个很重的操作,而使用自旋,就不会产生上下文切换,也不会阻塞线程,但是自旋操作也有缺点,就是说当一个线程因为获取不到锁而长期自旋时,会长时间的占用CPU资源,导致CPU资源的浪费,所以自旋必须要加上一个自旋次数来控制,并且,JVM还提供了一种操作,就是自适应自旋,通过上一次在同一个锁上的自旋次数来决定下一次的自旋次数,对于某个锁,如果自旋很少成功获取到锁,那之后,有可能直接省略掉自旋过程,直接阻塞线程
轻量级锁和重量级锁
Lock Record :锁记录,用户存储对象当前 Mark Word 的拷贝
使用CAS尝试更改对象的Mark Word更新为指向锁记录 ,如果更新成功,那么直接进入同步块继续执行即可,如果更新失败,表示有多个线程同时在竞争该所,此时轻量级锁必须膨胀为重量级锁,也就是互斥锁,后面获取锁的线程必须要阻塞
代码优化之锁清除
Java编译器在运行时,对检测不到的共享数据竞争进行锁清除,根据逃逸分析的数据支持,判断一个代码块,在堆上的数据都不会逃逸出去而被其他线程访问到,就可以直接把其当成栈上的数据,无需加锁
代码优化之锁粗化
因为频繁的进行同步互斥会导致不必要的消耗,JVM会直接将加锁同步的范围扩大到整个操作序列,这样就只需要加1次锁即可
CAS
CompareAndSet,比较并交换,该方法是由硬件提供的一种实现原子操作的方法,原子操作是一种不可分割的指令序列,CAS使用3个参数,内存地址,旧值,需要更新的新值,当内存地址上的值和旧值一样时,就将旧值更新为新值
PS:逃逸优化后续会填坑