暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

JDK对synchronized做了什么优化

杨遥 2021-06-26
1021


作者|杨遥


了解jdk1.6对锁优化之前我们先简单了解一下重量级锁,什么是重量级锁呢,看一下以下代码

        public void autoIncrement() {
    synchronized (this) {
    total++;
    }
    }

    JDK1.6之前synchronized还是重量级锁,也可以叫悲观锁或者互斥锁。为什么叫重量级锁呢,分析一下多线程竞争的资源的情况。

    我们知道每次只有一个线程能进入同步代码块,在JDK1.6之前其实是对当前对象内部有一个monitor对象进行加锁。如果线程一此时获取到锁了,其他线程则放到等待队列里面。这种原始的实现方式有性能问题,如进入等待队列里面的线程则会有,线程阻塞,上线文切换,操作系统线程调度,用户态内核态切换等。所以JDK1.6之前的锁也叫重量级锁。


    JDK1.6之后对synchronized做了以下锁优化。

    无锁-->偏向锁-->轻量级锁-->重量级锁

    • 无锁:当前方法没有线程竞争

    • 偏向锁:当前方法有多处同步代码块,且没有发生线程竞争

    • 轻量级锁:也叫自旋锁,乐观锁,CAS操作

    • 重量级锁:线程等待,排队


    了解锁升级之前我们必须先了解一个JAVA对象的组成。一个对象的组成分为三个部分。

    对象头:包含markword,指向类指针(jdk8也可以叫元数据指针),数组长度(如果当前不是数组,则没有此部分)

    实例数据:JAVA对象数据

    对齐填充字节:JAVA对象总体呈现大小总是8bit的倍数,方便操作系统寻址


    着重关注对象头里面的markword,其实锁升级的过程,都是通过对对象头里面markword的位数进行控制的,markword组成结构如下图。

    我们关注一下锁标志位和是否偏向锁。锁升级的过程如下

    无锁:是否偏向锁0,锁标志位01

            锁竞争非常小,甚至可能没有锁竞争。

          public void autoIncrement() {
      synchronized (this) {
      total++;
      }

      偏向锁:是否偏向锁1,锁标志位01

              此时锁竞争非常小的情况下通过记录对象里面的线程id,第一处释放锁以后,第二次加锁判断加锁的id和记录的线程id是否一样,一样的话直接执行,则是开销非常小,简单记录一下线程id即可

           public void autoIncrement() {
        synchronized (this) {
        total++;
        }
        synchronized (this) {
        total++;
        }
        }

        轻量级锁:锁标志为00

            由偏向锁产生了一定量锁竞争,此时锁升级为轻量级锁。通过自旋和比较的过程来执行代码块。

        重量级锁:锁标志10

                此时大量线程来加锁,通过轻量级锁已经有大量的CPU空转,此时非常耗CPU,甚至cpu打满。此时还不如把线程放入到等待队列,至少不会大量消耗CPU,所以此时才会升级到重量级锁。


        总结:

            无锁:代码块没有锁竞争,此时是无锁。

            无锁升级偏向锁:代码块锁竞争小或者同一个线程多次加锁,此时还不需要升级为轻量级锁,通过记录线程id就能搞定。

            偏向锁升级轻量级锁:产生了一定锁竞争,此时竞争不是特别大。只需要通过自选和cas的比较操作就能完成。

            轻量级锁升级重量级锁:此时大量线程产线锁竞争,是的轻量级锁对cpu产生空转,所以此时将等待加锁的线程放入队列


        文章转载自杨遥,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

        评论