【面试八股文】每日一题:Synchronized源码面试25连问?

 

1、jdk1.6版本中提出了偏向锁和轻量级锁,关于用途,简单说下吧?

先说偏向锁,当我们使用synchronized关键字锁定某个对象的时候,如果这个(锁)对象处于匿名偏向状态,也就是(锁对象)markword这个64位的二进制数中,表示偏向线程的bit位都是0并且锁状态位表示的是偏向锁状态,这个时候,线程获取时,直接在锁对象markword高位内存储当前线程内存地址,这块是使用cas比较并替换完成的,如果设置成功了,那当前这把锁就是偏向当前线程的锁了,,然后当前线程就可以直接执行同步代码块了,获取锁的成本非常低,基本上两个步骤。第一个步骤是向当前线程栈内添加一条锁记录,让锁记录中的锁标识指向当前锁对象,这一块不存在并发问题的,所以不需要使用cas,第二步是通过cas设置锁对象的markword存储当前线程地址,这一步必须使用cas,因为可能有多个线程抢占锁。

 

2、偏向锁无法提供线程互斥,为什么还要引入?

为了程序的健壮性会使用synchronized来确保这块逻辑在并发环境下不出问题,但是实际情况往往不是这样的,觉大部分情况下,使用synchronizeed包裹着的代码块,它实际运行环境根本没有多线程场景,可能只有一个线程运行,,这种情况特别多,如果没有偏向锁的话,直接使用轻量级锁会有一定的性能损耗,因为轻量级锁加锁或者解锁,它都需要一次cas操作,编向锁解锁时不需要修改对象头的markword,就少一次cas操作

 

3、某个线程获取到锁对象的偏向锁之后,执行完同步代码块之后,会释放这个偏向锁吗?

退出同步代码块,对应字节码指令是monitorexit,然后jvm处理这个指令的时候,第一步是将当前线程栈内与当前锁对象相关的锁记录全部拿到,将最后一条锁记录释放掉,通过检查lock对象的markword,如果当前锁对象是偏向状态,就啥也不做,也就是说,就算线程退出同步代码块了,该锁对象仍然保留了偏向当前线程的偏向锁,

4、这样做有什么好处?

偏向锁退出的时候,锁仍然保留偏向状态好处,下次这个线程再去获取这把锁时,成本会更低,j仅仅只需要比对一下,当前这把锁是否偏向自己就好了,整个加锁的过程不涉及到一条cas指令。

 

5、使用了偏向锁是否就一定会提升性能?

不一定,比如说,某个锁对象,因为之前被线程a获取过,它的锁状态是偏向线程A,那假设线程B也要获取这个锁对象,因为锁对象并不偏向线程B,所以会造成锁升级的逻辑,线程B会触发偏向锁到轻量级锁的升级,这个逻辑会检查很多状态来保证锁的正确性,这块比较麻烦,所以说如果没有偏向锁的存在的话,这个升级过程可以避免掉的,

 

6、锁升级过程等下说?

 

7、说说轻量级锁?

使用synchronized包裹着的代码,实际运行情况出现并发的概率并不高,绝大部分情况下是线程A运行完同步代码然后线程B再运行同步代码,这些线程交替运行同步代码,实际情况下,不存在线程并行,,在这种情况下,如果没有轻量级锁,带来的后果就是提前创建了重量级锁monitor对象,创建重量级锁比较耗费资源的,创建重量级锁应该等到实在没办法了,确实有线程并行的这种情况下才去创建,轻量级锁介于偏向锁和重量级锁之间,用于的场景就是多个线程交替执行同步代码块的逻辑,真实环境处于不存在线程并行时使用的

 

8、轻量级锁和偏向锁的最大区别是什么?

一个线程和多个线程交替运行

 

9、假如当前锁对象目前处于无锁状态,请说下,从无锁到轻量级锁的过程吧

加锁对于的字节码仍然是monitorenter指令,jvm执行这个指令之前,它第一件事还是向当前线程栈内插入一条锁记录。锁记录内的锁引用字段保存锁对象地址,第二件事是让当前锁对象生成一条无锁状态的markword值,学名叫做displacedmarkword,并且让这个生成的无锁状态的displayedmarkword值保存到当前这条锁记录的displaced字段内,第三件事使用cas的方式去设置当前锁对象的markword值,修改为当前线程持有轻量级锁状态,如果当前对象的markword就是无锁状态,那么这一步肯定会修改成功,当前锁对象就从无锁状态变为轻量级锁状态了,并且从当前锁的markword可以看出持锁线程就是当前线程

 

10、可重入锁,那当前线程再次使用synchronized去锁定这把锁,从jvm角度去看,都做了什么事?

首先,jvm解析器收到的字节码指令,仍然是monitor指令,在解析执行这个指令之前呢,它第一件事还是向当前线程栈内插入一条锁记录。锁记录内的锁引用字段保存锁对象地址,第二件事是让当前锁对象生成一条无锁状态的markword值,学名叫做displacedmarkword,并且让这个生成的无锁状态的displayedmarkword值保存到当前这条锁记录的displaced字段内,第三件事使用cas的方式去设置当前锁对象的markword值,因为当前锁对象的markword值处于轻量级锁状态,所以这一步cas会失败,因为它采用的比较值是无锁状态的ma'm'r'kword值,这一步cas失败以后,当前线程需要检查为什么失败,首先要检查的就是锁重入的情况,如果(锁对象)markword内表示锁持有者的bit位的值指向当前线程空间的话,说明当前锁持有者就是当前线程,这是一次锁重入操作。锁重入的话,只需要把刚刚在栈内插入的那条锁记录的displaced字段值置为空就可以了,稍微补充下,锁重入次数是靠线程栈内指向当前锁的锁记录数量来完成的,当前线程每重入一次锁,就会在这个线程栈内插入一条关于这把锁的锁记录

 

 

 

 

11、说说轻量级锁释放锁的大概逻辑?

释放锁的字节码是monitorexit,jvm处理这条指令的时候,首先从当前线程栈中找到最后一条锁引用字段

指向当前锁对象的锁记录,并且将这条锁记录字段设置为null(这一步就是释放锁记录),这一步不需要cas,因为线程栈内的数据,不存在多线程访问场景(栈封闭特性),这一步完事以后,再检查这条锁记录的displaced字段是否有值,如果没值,说明这条锁记录是锁重入时存放的,锁重入只要将这一条锁记录释放,对应的逻辑就是锁记录中的锁引用字段设置为null,这样就完成了一次锁退出

 

12、刚才说了锁被重入之后的一次锁退出,还不是锁被完全释放,那假如说当前这个线程,它正在最后一次退出这把锁,完全释放这把锁,这块说一下?

首先从当前线程栈中找到最后一条锁引用字段指向当前锁对象的锁记录,并且将这条锁记录字段设置为null,然后在检查锁记录,因为轻量级锁或者重量级锁第一次加锁时候,在线程栈内插入的锁比较特殊,第一次加锁时的这把锁记录它保存了一个叫displacedmarkword值,这个markword值表示的状态对应无锁状态,接下来,当前线程需要把锁记录内的displaced值通过cas的方式设置到当前锁对象的markword中,这一步设置成功后,就相当于完全释放了,如果失败,当前锁已经被升级到重量级锁或者现在正处于膨胀状态了,需要走重量级锁的退出逻辑了

 

13、假设当前锁状态处于偏向锁,并且偏向线程A也就是说,锁的markword内存储的数据是偏向线程A的偏向锁数据,那这种情况下线程B怎么获取这把锁呢?

从monitorenter这条字节码开始,线程B在执行monitorenter指令前,仍然是向当前线程栈内插入一条锁记录,锁记录的锁引用字段指向当前这个锁对象,然后下一步,检查当前锁状态,发现锁处于偏向锁状态,并且偏向的线程也不是当前线程,当前线程B会提交一个撤销偏向锁的任务,这个任务提交到了VM线程的任务队列中,VM线程会在后台不停的处理任务队列内的任务,VM取一个任务之后,会看当前任务是否需要在safepoint状态下执行,如果需要在safepoint下执行,则等待到safepoint时执行,撤销偏向锁任务,首先的检查当前jvm所有存活线程,主要是检查当前持有偏向锁的线程是不是还存活着,前面聊偏向锁退出的时候,说过了偏向锁退出的时候并不把锁对象的偏向状态给置回,就算偏向锁完全退出,它仍然保留偏向状态,如果偏向锁偏向的线程已经消亡了,直接将markword改为无锁状态或者匿名偏向锁状态,大部分是修改为无锁状态,如果偏向锁线程仍然存活着,这种情况,首先需要计算出来偏向线程是不是还在同步代码块内,通过遍历偏向线程栈内的锁记录来判断,如果栈内有一条锁记录的锁引用字段指向当前锁对象,那么说明偏向线程仍然处于同步代码块以内,否则,说明偏向线程已经跳出同步代码块了

 

14、safepoint安全点,这是个什么状态?

就是jvm出现这个状态的时候,所有的线程都处于阻赛状态,不能向下执行程序,也就是说线程的栈和共享的堆,在safepoint时,处于非完全冻结,只有后台的vm线程处于运行状态,vm线程它可以执行一些特殊的任务,比如说fullgc任务或者撤销偏向锁等等的任务,撤销偏向锁的任务就必须在安全点内执行,因为撤销的过程中会修改持有锁的线程的栈数据,如果不在安全点内执行,会有并发问题

 

 

15、偏向线程已经跳出同步代码块的逻辑

直接将锁对象的markword设置为无锁状态或者匿名偏向状态就可以了

 

16、偏向线程仍然处于步代码块的逻辑

这个需要将偏向锁升级为轻量级锁,首先遍历线程栈,找到锁记录指向当前锁对象的第一条记录,内存中从低到高的第一条记录(初次加锁的锁记录),修改这条锁记录的displaced字段的数据为无锁状态的markword值,锁完全释放时会使用这个值,在然后,修改锁对象的markword为轻量级锁状态,并且保存这条锁记录的内存地址,其实也就是持锁线程内存空间的一个位置,后面可以根据这个空间位置来判断是不是当前线程持锁,这样就完成了偏向锁到轻量级锁的升级

 

17、原偏向线程处于同步代码块,线程B仍然想竟如同步代码块,轻量级锁是线程交替执行,所以此时轻量级锁也不好使了?

如果偏向锁的偏向线程仍然处于持锁状态,其她线程请求进入同步代码块,首先会触发一次偏向锁升级轻量级锁的逻辑,升级后,外部线程会继续自旋检查,它会检查出当前锁对象处于轻量级锁状态,在触发一次锁升级,再将轻量级升级为重量级锁,然后利用重量级锁提供的锁提供的互斥性来保证线程安全

 

 

18、聊下重量级锁?

重量级锁就是强制将并行的线程依次通过同步代码块的实现,其实这个重量级锁和AQS的实现很相似,都是遵循管称模型,

 

19、说下重量级锁ObjectMonitor内部的主要数据结构有哪些吧?

核心的有三个队列,其中两个是等待队列,分别是竞争队列和EntrylIst队列,还有一个是阻塞1队列WaitList,还有一个很重要的当前持锁的线程引用owner字段

 

20、三个队列什么用途?

 

竞争队列和EntrylIst队列,它用来存放等待获取锁资源线程的

WaitList这个是用来处理持锁线程调用锁对象wait()方法使用的,也就是说,持锁线程调用了锁对象wait()方法之后,会把直接封装成一个waiter节点插入到waitlist队列中,然后会唤醒一个等待节点,在然后这个执行wait操作的线程就会使用patk操作将自己挂起,直到其他线程通过使用notify或者notifyall操作将它从waitlist队列中移动到竞争队列,竞争队列和EntrylIst队列里面的线程最终会被持锁线程释放锁时选择一个去唤醒去抢占锁,我感觉这块和AQS与Condition条件队列的组合没太大区别

 

21、大概说下,线程获取锁的流程

如果是重量级锁的话,锁对象的markword保存的数据是管程对象内存位置和重量级锁状态,通过markword保存的数据是管程对象内存位置和重量级锁状态,抢占锁的线程到管程内去抢占锁,线程进入管程后,首先通过自旋的形式进行几次尝试获取锁,其实就是通过cas将管程内的owner字段设置为当前线程,如果设置成功,说明锁抢占成功,直接返回就行了,不然的话,说明这个锁有其它线程占用着,这个时候,这个线程就把自己封装成waiter节点插入竞争队列内,插入成功后,这个线程就挂起,直到其它线程唤醒为止

 

 

22、持有重量级锁的线程,它完全释放锁都做了什么?

它释放锁,就是将管程对象ObjectMonito的owner字段设置为null,释放完锁之后,它需要检查竞争队列和EntryList,看看是否有等待锁的线程,如果有,它会根据不同的策略选择唤醒一个线程去抢占锁

 

23、重量级锁是不是公平锁?

不是,因为持锁线程释放掉锁之后,去竞争队列和EntryList内唤醒下一个继承者之前,这个窗口期间,外部再来线程是可以尝试获取锁的

 

24、锁膨胀成重量级锁的过程?

三种情况:

第一张情况是有线程调用轻量级锁或偏向锁对象的hashcode方法,因为锁对象处于轻量级或者偏向状态时markword是无法存储hash值的,这个时候会膨胀,然后重量级锁可以提供一些空间来存储hahs值

第二种情况  持锁的线程调用锁对象的wait()方法会导致膨胀,因为锁对象处于其它状态时是没有管程对象存在的,没有管程存在就没地方放线程节点

第三种 当前锁状态是轻量级锁状态,然后其它线程竞争锁时真正产生并发了

 

25、您说下轻量级锁状态时,然后其它线程竞争导致的锁膨胀过程

首先这个,竞争线程因为获取锁失败会走上锁膨胀的逻辑,在锁膨胀的逻辑里,会判断出来当前锁是轻量级锁状态嘛,所以接下来这个线程会获取一个空闲的管程对象,在然后通过CAS修改锁状态为膨胀中,如果cas失败,说明其它线程正在膨胀或者已经膨胀结束了,再次自旋获取就可以了,如果CAS操作成功,那么当前线程需要给这个锁升级操作,主要是将管程对象的owner设置为原轻量级持锁线程,然后在将持锁线程的第一条锁记录内存储的displaceddmarkword保存到管程对象内,可能锁降级时会用到,把那个栈顶markword拷贝到重量级锁之后,再下一步设置锁对象的markword为重量级锁,包括两个信息

一个是重量级锁对象内存位置,另一个是重量级锁状态,再之后,其它线程再去请求获取这把锁,就会找到这个重量级锁对象,走管程内的逻辑

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 24
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值