锁的类别
锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁只能升级不能降级,目的是为了提高获得锁和释放锁的效率。下面是不同锁状态下对象头的Mark Word结构。
无锁状态
- 无锁状态即每个线程都可以执行程序,不需要加锁解锁过程。
偏向锁
- 研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。
CAS算法
了解偏向锁之前,我们先看一下什么是CAS算法:当多个线程并发的对内存中的数据进行修改时,有且只有一个线程能成功,其他的都会失败,失败的线程会进行失败重试。
偏向锁的获取
当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需要再测试一下对象头的Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。
简单来说,就是先判断对象头和当前线程中偏向锁的线程ID是不是指向当前线程,如果是,就不需要再次获取;如果不是,检查对象头中有没有偏向锁;如果有偏向锁,将对象头中的线程ID指向当前线程;如果没有偏向锁,就使用CAS进行竞争偏向锁。
偏向锁的撤销
当其他线程竞争偏向锁时,持有偏向锁的线程才会释放锁。
撤销过程:
- 1、等待全局安全点(在这个时间点上没有正在执行的字节码);
- 2、暂停拥有偏向锁的线程,检查是否存活;如果存活,先将当前持有偏向锁的对象执行完,再遍历其他偏向锁的线程;当前线程的锁记录和对象头的MarkWord要么重新偏向其他线程,要么无锁或标记对象不适合作为偏向锁。
轻量级锁
加锁
先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
解锁
使用原子的CAS操作将当前持有锁的线程中的锁记录替换回对象头的MarkWord,如果成功说明没有其他锁竞争;如果不同,说明当前锁存在竞争,锁就会膨胀为重量级锁;这时由于当前线程持有重量级锁,其他竞争的线程都会被阻塞;此时当前线程释放锁并且唤醒等待的线程。
由于自旋会消耗cpu,为了避免无用的自旋,所有试图获取重量级锁的线程都会被阻塞;直到当前持有锁的线程释放锁,这些阻塞的线程就会重新争夺锁。
重量级锁
特点:当A线程试图获取重量级锁时,如果发现重量级锁已经被线程B占用,则将当前线程A挂起,等待B线程释放锁之后唤醒A线程。