1.对象锁和类锁

当synchronized修饰静态方法或代码块参数为Class|固定值,锁为类锁,作用整个类(该类的所有对象都有效)。
当synchronized修饰实例方法或代码参数为this时,为对象锁,必须为同一个对象锁有效。

2.什么是可重入锁

某个线程已经获得了某个锁,允许再次获得锁,就是可重入锁。如果不允许再次获得锁就称为不可重入锁。
synchronized就是可重入锁。
但可重入锁不仅仅只有synchronized。还有ReentrantLock也是可重入锁。
代码示例:

  1. public class Test05 {
  2. //synchronized为可重入锁
  3. public void test(){
  4. synchronized (this){
  5. for (int i = 0; i < 10; i++) {
  6. System.out.println("外层加锁"+i);
  7. }
  8. synchronized (this){
  9. for (int i = 0; i < 5; i++) {
  10. System.out.println("内层加锁"+i);
  11. }
  12. }
  13. }
  14. }
  15. }
  1. public class Test06 {
  2. public static void main(String[] args) {
  3. Test05 test05=new Test05();
  4. new Thread(()->{
  5. test05.test();
  6. }).start();
  7. }
  8. }

3.可重入锁底层原理

可重入锁底层原理特别简单,就是计数器。
当一个线程第一次持有某个锁时会由monitor(监控器)对持有锁的数量加1,当这个线程再次需要碰到这个锁时,如果是可重入锁就对持有锁数量再次加1(如果是不可重入锁,发现持有锁为1了,就不允许多次持有这个锁了,阻塞),当释放锁时对持有锁数量减1,直到减为0,表示完全释放了这个锁。

4.synchronized底层原理(小组Leader级面试题)

在Java早期,synchronized叫做重量级锁,加锁过程需要操作系统在内核态访问核心资源,因此操作系统会在用户态与内核态之间切换,效率很低下。于是JDK1.6之后,JVM为了提高锁的获取与释放效率,对synchronized进行了优化,引入了偏向锁和轻量级锁,根据线程竞争情况对锁进行升级,在线程竞争不激烈的情况避免使用重量级锁。

无锁:对象头中有31bit的空间来存储对象的hashcode,4bit用于存放对象分代年龄,1bit来表示是否是偏向锁,2bit存放锁标志位,偏向锁位与锁标志位合起来“001”就代表无锁。无锁就是没有对任何资源进行锁定,所有线程都能访问并修改资源。
偏向锁:对象头中记录了获得偏向锁的线程ID,偏向锁与锁标志位合起来“101”就代表偏向锁。有研究发现,在大多数情况下,锁很少被多个线程同时竞争,而且总是由同一个线程多次获得,因此只需要将获得锁的线程ID写入到锁对象Mark Word中,相当于告诉其他线程,这块资源已经被我占了。当线程访问资源结束后,不会主动释放偏向锁,当线程再次需要访问资源时,JVM就会通过Mark Word中记录的线程ID判断是否是当前线程,如果是,则继续访问资源。所以,在没有其他线程参与竞争时,锁就一直偏向被当前线程持有,当前线程就可以一直占用资源或者执行代码。
自旋锁(轻量级锁):一旦有另外一个线程参与锁竞争,偏向锁就会升级为自旋锁,此时撤销偏向锁,锁标志位变为“00”。竞争的两个线程都在各自的线程栈帧中生成一个Lock Record空间,用于存储锁对象目前Mark Word的拷贝,用CAS操作将Mark Word设置为指向自己这个线程的LR(Lock Record)指针,设置成功者获得锁,其他参与竞争的线程如果未获取到锁,则会一直处于自旋等待的状态,直到竞争到锁。
重量级锁:长时间的自旋操作是很消耗CPU资源的,为了避免这种盲目的消耗,JVM会在有线程超过10次自旋,或者自旋次数超过CPU核数的一半(JDK1.6以后加入了自适应自旋-Adaptive Self Spinning,由JVM自己控制自旋次数)时,会升级到重量级锁。重量级锁底层是依赖操作系统的mutex互斥锁,也就是有操作系统来负责线程间的调度。重量级锁减少了自旋锁带来的CPU消耗,但是由于操作系统调度线程带来的线程阻塞会使程序响应速度变慢。

在markword中标记锁的类型
普通对象在内存中的结构分为多部分,第一部分称为markword,共64位。在对应锁对象的markword字段的低位字段标记锁的类型。
1.png