简介
1、失败后进行锁膨胀:偏向锁 --> 轻量锁 --> 重量锁。
2、偏向锁:认为没有竞争,每次都是同一个线程获取的锁,所以第一次通过CAS后,把线程id放到锁对象Mark Word后,以后每次都不需要CAS操作。
3、轻量级锁:认为没有竞争,每次都是不同线程轮流获取的锁,所以利用CAS拷贝Mark Word副本对象到线程的栈中存储,以后每次通过CAS操作减少使用互斥量的开销。
自旋锁
多线程同步时,频繁挂起和恢复线程开销比较大,让线程执行一个忙循环(自旋),就是所谓的自旋锁。jdk1.6后引入了自适应自旋锁,自旋时间由前一次在同一个锁上的自旋时间和锁的拥有者的状态来决定。
锁消除
虚拟机及时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。如:字符串通过“+”进行连接的时候,会转换为Stringbuilder,Stringbuilder中的锁可能会被消除。
锁粗化
锁粒度尽可能小,如果Stringbuilder的append()中的锁比较频繁,可能会在整个方法加一次锁。
轻量级锁
1、概述:提升程序同步性能的依据是“对于绝大部分的锁,在整个同步周期内都是不存在竞争的”这一经验法则。如果没有竞争,不同线程可轮流通过轻量级锁,CAS操作成功避免了使用互斥量的开销;但如果确 实存在锁竞争,除了互斥量的本身开销外,还额外发生了CAS操作的开销。因此在有竞争的情况下, 轻量级锁反而会比传统的重量级锁更慢。
2、对象头分为两部分信息,1、用于存储对象自身运行时的数据,如哈希码、GC分代年龄等这部分数据长度在32位和64位虚拟机中分别为32bit和64bit,称作mark word.
3、虚拟机先将在当前线程的栈帧中建立名为锁记录(lock record)的空间,用于存储锁对象目前的mark word的拷贝(displaced mark word),然后虚拟机将cas操作尝试将对象的mark word更新为指向lock record的指针,更新成功,那么这个线程拥有了该对象的锁,对象处于轻量级锁状态。更新失败,会检查对象mark word是否指向当前线程的栈帧,则直接进入同步代码块继续执行。否则说明锁已经被其它线程抢占。如果存在多个线程同时竞争一把锁,轻量锁就会膨胀为重量锁。
解锁
轻量锁的解锁过程也是利用 CAS
来实现的,会尝试锁记录替换回锁对象的 Mark Word
。如果替换成功则说明整个同步操作完成,失败则说明有其他线程尝试获取锁,这时就会唤醒被挂起的线程(此时已经膨胀为重量锁
)
轻量锁能提升性能的原因是:认为大多数锁在整个同步周期都不存在竞争,所以使用 CAS
比使用互斥开销更少。但如果锁竞争激烈,轻量锁就不但有互斥的开销,还有 CAS
的开销,甚至比重量锁更慢。
偏向锁
如果轻量级锁是无竞争情况下使用CAS操作去消除同步使用的互斥量,那偏向锁就是在无竞争环境下把整个同步都消除掉。
偏向锁的特征是:认为锁不存在多线程竞争,并且应由同一个线程多次获得锁。
当线程访问同步块时,会使用 CAS
将线程 ID 更新到锁对象的 Mark Word
中,如果更新成功则获得偏向锁,并且之后每次进入这个对象锁相关的同步块时都不需要再次获取锁了。
释放锁
当有另外一个线程获取这个锁时,持有偏向锁的线程就会释放锁,释放时会等待全局安全点(这一时刻没有字节码运行),接着会暂停拥有偏向锁的线程,根据锁对象目前是否被锁来判定将对象头中的 Mark Word
设置为无锁或者是轻量锁状态。
轻量锁可以提高带有同步却没有竞争的程序性能,但如果程序中大多数锁都存在竞争时,那偏向锁就起不到太大作用。可以使用 -XX:-userBiasedLocking=false
来关闭偏向锁,并默认进入轻量锁。
乐观锁
对并发操作产生的线程问题持乐观态度,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
悲观锁
不管三七二十一,直接上独占锁,就像synchronized
分段锁
把一个锁分解为多个锁
分解锁
锁上存在适中而不是很激烈的竞争时,将一个锁分解为两个锁
读写锁
ReentrantReadWriteLock:使用场景是读多写少时。
1、可重入
2、默认是非公平的锁
3、线程持有写锁不能在不释放写锁的情况下获得读锁,反之可以。
4、如果锁由读线程持有,另一个线程请求写锁,那么其他读线程都不能获得读取锁,直到写线程释放写入锁。