首页 🍄Java

synchronized在jdk 1.6之前都是直接通过内核来做加锁释放锁的操作,但是从用户态到内核态切换的花销还是挺大的所以在后面进行了一些优化

锁膨胀

锁膨胀是指synchronized从无锁->偏向锁->轻量级锁->重量级锁的过程,在1.6之前synchronized都是重量级锁,在有了锁膨胀之后synchronized就有了无锁,偏向锁,轻量级锁的方式,这个时候进行锁的操作就不需要再进行用户态内核态的转换。

无锁

无锁就是没有对资源进行锁的限制,所有线程一起获取资源,但同一时刻只能有一个线程进行修改。

偏向锁

初次运行到synchronized时通过cas锁修改对象头中的锁标志为偏向锁,偏向锁的意思就是偏向第一个获取到锁的线程,当该线程执行完一段同步代码后不释放锁,当第二次执行同步代码时会判断获取锁的线程是不是当前线程(在对象头中有当前获取锁的id),如果是的话就不需要再获取锁了,由于没有释放锁和再次获取锁的过程,偏向锁的效率极高。
偏向锁是指当同步代码一直被同一个线程访问时,在后续该线程访问同步代码时就可以自动获取锁。
当别的线程试图竞争锁的时候,偏向锁才开始释放锁,释放锁需要在局部安全点(在这一时刻没有字节码在执行)。

轻量级锁

当有别的线程竞争偏向锁时偏向锁就会升级为轻量级锁,在轻量级锁下继续竞争没有获取到锁的线程会进入自旋状态,即通过循环来一直获取锁,如果长时间自旋没有获取到锁的话会一直占用cpu并且做不了其他事,这种情况也比较耗费资源。
轻量级锁适用于锁竞争不大,自旋时间不长的情况。

重量级锁

如果锁竞争很大自旋时间过长则会升级为重量级锁,重量级锁在获取锁的过程中获取失败就会把自己挂起等待唤醒。

锁消除

jvm根据逃逸分析判断加锁的代码是否会出现锁竞争的情况,如果不会出现锁竞争则会将锁清除
Snipaste_2022-11-20_14-38-42.png

Snipaste_2022-11-20_14-39-19.png
可以看到上面的代码使用的是加锁的StringBuffer,编译到字节码变成了StringBuilder这个没有锁的对象。

锁粗化

锁粗化是指将多个连续的加锁、释放锁的操作合并形成一个更大范围的锁
我只听说锁“细化”可以提高程序的执行效率,也就是将锁的范围尽可能缩小,这样在锁竞争时,等待获取锁的线程才能更早的获取锁,从而提高程序的运行效率,但锁粗化是如何提高性能的呢?
一系列的加锁、解锁过程也会带来很大的开销,就比如

public void method() {
    for (int i = 0; i < 10; i++) {
        // lock
        System.out.println(i);
        // unlock
    }
}

这段代码锁的范围很小,经常获取锁,释放锁性能开销很大。如果我们在for外面加上一层锁性能就会得到提高

public void method() {
    // lock
    for (int i = 0; i < 10; i++) {
        System.out.println(i);
    }
    // unlock
}

如果检测到同一个对象执行了连续的加锁和解锁的操作,则会将这一系列操作合并成一个更大的锁,从而提升程序的执行效率。

自适应自旋锁

自旋锁是指通过一个死循环来获取锁,普通的自旋锁有一个阈值,达到阈值之后可能会放弃获取锁进入挂起状态。
自适应自旋锁会根据之前获取到锁的情况来改变自旋的次数,如果上一个自旋获取到了锁,就说明这次也可能获取到锁,自选的次数就会增加。如果上次自选没有获取到锁,就说明这次也可能获取不到锁,自旋的次数就会减少。




文章评论