大厂面试(三)请描述synchornized锁是如何升级的

Synchronized锁的升级过程

先来看一张图

大厂面试(三)请描述synchornized锁是如何升级的_第1张图片

先说下偏向锁的概念,偏向锁会认为第一个持有锁的线程是非常重要的一个线程,它会偏向这个线程。

偏向锁和轻量级锁(自旋锁)都是用户空间的锁,不需要向操作系统内核去申请重量级锁,所以它们在竞争

比较轻度的时候比重量级锁效率高。听不懂没关系,举个例子:

大厂面试(三)请描述synchornized锁是如何升级的_第2张图片

平时我们上厕所,把门锁上之后,只有一个人能进来,也就是只有一个线程能干活儿,不能多个人同时干活儿,不能说他做你大腿上一起干活儿,这是绝对不行的,所以说你必须得持有这把锁,你才能进来。jdk最早是找厕所管理员申请这把锁,给你锁了,你把门锁上,你才能干活儿,偏向锁什么概念呢?比如你去厕所,第一次进去时里面没有人,偏向锁就是在门上贴个标签,告诉外面的人,里面有人干活儿,这把锁就归我了,如果是重量级锁是不是有很多人去管理员那竞争啊,偏向锁没有锁竞争的过程,只要我能把标签贴上,当然贴的过程是CAS实现的,不知道CAS的请看我前一篇文章。当门上贴上你的标签后也就是有了偏向锁后,外面的人等待,你一出去,他的标签直接贴门上,然后进来干活。

为什么要有偏向锁?多数synchronized方法,在95%的时间里是只有一个线程在运行的,只有5%的时间会有锁竞争,那我没必要在持有这把锁的时候就设立一个锁竞争的机制,因为大部分时间里是没有竞争的,这么做完全是浪费,所以第一个线程进来之后,直接把线程ID往门上一贴,这把锁就归你了,没有竞争,95%的时间里,效率就提升了。

这时候你会发现一个问题,这个厕所不能只给你一个人用啊,外面张三、李四、王五…都在那排队呢,你这标签什么时候撕掉换我干活啊,这个时候该锁升级了。

大厂面试(三)请描述synchornized锁是如何升级的_第3张图片

从偏向锁升级到轻量级锁也叫自旋锁,也叫无锁。

还拿厕所举例,假设你在厕所干活儿,门上贴着你的标签,门外面张三、李四也想上厕所干活,这时候锁升级了,撤销偏向锁,把你的标签从门上撕下来,可是你还在里面干活儿,怎么办呢?门外俩哥们开始竞争了,他俩不是在等待,而是自旋,也就是CAS的操作,假设你进去之后,这个值是1,出来之后改成0,门外的哥们开始取值,发现不是0,继续取发现还不是0,直到第3次发现值是0了,这俩哥们开始把自己的标签贴上,这个贴的过程就是CAS,谁先贴上这把锁归谁。

那既然有偏向锁了,那为什么要有重量级锁?

重量级锁和轻量级锁的区别:

重量级锁之前的文章已经说过了,就是想要获得一把锁,必须经过操作系统内核去调度,那么操作系统是怎么线程调度的呢?

重量级锁是有一个等待队列的,什么意思?就是所有想得到这把锁的线程必须要先进等待队列,大家记不记得我们写代码的时候一定注意过wait()notify all, wait()的意思就是让这个线程进入等待队列,等待这把锁什么时候释放我什么时候再去抢,等待队列有个特点就是里面的线程是不做任何操作的,什么都不干,就是等,CAS是自旋,一直取值判断,这是消耗CPU资源的。这就是重量级锁和自旋锁最重要的区别。

总结:当有很多线程去竞争一把锁时,重量级锁有等待队列,全部放在等待队列里安安静静的等待锁释放,相比于自旋锁,所有的线程都在空转,一直循环,这时候,重量级锁的效率就体现出来了。

这时候你就明白图中为什么轻量级锁要升级到重量级锁,就是因为在某些特定情况下,竞争越来越激烈的时候,轻量级锁效率就不高了。

比如说,你在厕所干活儿呢,便秘,外面有一万个人自旋,CPU能受得了吗,所以这时候还不如重量级锁,所有人老老实实的给我排队去,什么时候我出来了,我再叫醒你,你再干活儿。

轻量级锁:适用于线程不是特别多,要执行的操作时间不长。

重量级锁:适用于线程特别多,要执行的操作执行时间很长。

那轻量级锁(自旋锁)什么时候开始升级为重量级锁呢?

竞争加剧:jdk1.6之前,如果一个线程自旋次数超过10次,或者等待的线程超过CPU核数的二分之一,比如说我8核的,有5个线程都在这转圈,这个时候就是竞争加剧了就会升级为重量级锁。jdk1.6做了升级优化,叫自适应自旋,就是不需要你判断自旋次数和等待的线程数了,JVM自己控制。

当我new一个对象的时候,什么时候打开偏向锁呢?

001,是图中的无锁态。这是直接new出来的对象,如果我new之前我先睡5秒,看看什么样

public static void main(String[] args) throws InterruptedException {
    Thread.sleep(5000);
    Object o = new Object();
    System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION            VALUE
      0     4        (object header)        05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)        00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)        e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

发现markword最低位字节是101

大厂面试(三)请描述synchornized锁是如何升级的_第4张图片

再看图,锁状态是偏向锁启动,神马情况?我new出来的对象不是001吗,睡了5秒就编程101了,也就是说匿名偏向是过了一段时间才会打开,默认是4s,这4s干了什么呢,Java虚拟机启动,对内存初始化,new对象的时候是不是好多对象都争抢同一块内存啊,那我明知道会有很多线程会发生竞争,那我何必去打开偏向锁呢,所以它进行了一个优化,延迟4s再打开偏向锁,也就是匿名偏向,因为无论是普通对象还是匿名偏向状态,以后都有可能去加偏向锁的,所以4s后我直接就是匿名偏向,没必要再从普通对象改成匿名偏向,它们引用都是一样的,只是标注不一样。

你可能感兴趣的:(面试)