Java关于synchronized的一些问题

0. 介绍

——本文内容为自己在学习JVM以及多线程过程中,遇到的一些问题的总结,可能存在一些问题,欢迎指正;

参考文章:

深入理解Java并发之synchronized实现原理——zejian

Java对象的对象头、偏向锁、轻量级锁、重量级锁——Code@Z

1. 关于synchronized:MarkWord在32位JVM中的结构如下

Java关于synchronized的一些问题_第1张图片

2. 什么是monitor?每个对象都绑定唯一的monitor嘛?

——Java虚拟机中的同步是基于进入和瑞出管程(monitor)对象来实现的,因为每个对象都持有唯一的一把锁,所以每个对象也都绑定唯一的monitor对象,在Java虚拟机中,monitor是由ObjectMonitor来实现的。

3. 多线程之间互相抢占锁的原理是什么?

——前面提到,ObjectMonitor实现了Java多线程之间的同步。事实上在ObjectMonitor中有两个队列,_WaitSet和_EntryList,用来保存ObjectMonitor对象列表(封装过的线程对象)。

——当多线程访问时,会优先进入_EntryList,然后有一个线程获取到目标对象的锁,即_Owner指向该线程同时计数器count(应该是ObjectMonitor中的一个字段,用于协助实现重入)置为1,每次重入计数器都会加1;如果调用wait()方法,当前线程会释放锁,_Owner指向空,计数器归0,同时进入_WaitSet等待唤醒。

Java关于synchronized的一些问题_第2张图片

4. 从字节码的角度来理解synchronized

——在同步代码块中,编译器识别到synchronized会在相应的位置上将其编译成字节码指令:monitorenter,同时在同步代码块结束处添加字节码指令:monitorexit;

——同步方法并不是通过monitorenter和monitorexit来实现同步的;

5. 同步方法的实现原理和同步代码块有什么不同?

——同步方法中,会在flags字段中设置 ACC_SYNCHRONIZED 访问标志,执行方法时,如果发现有该标识,会先去获得目标对象的锁,再去执行方法中的代码体;

6. synchronized锁的弊端?

——synchronized是重量级锁,效率低下,因为监视器锁是依赖于操作系统底层的Mutex Lock(互斥锁)来实现的,而操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高。

7. synchronized的优化?——引入了轻量级锁和偏向锁

  • 锁的状态有4种:无锁,偏向锁,轻量级锁,重量级锁

  • 偏向锁:在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了减少同一线程获取锁(会涉及到一些CAS操作,耗时)的代价而引入偏向锁。偏向锁的核心思想是,如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。

  • 轻量级锁:倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的),此时Mark Word 的结构也变为轻量级锁的结构。轻量级锁能够提升程序性能的依据是“对绝大部分的锁,在整个同步周期内都不存在竞争”,注意这是经验数据。需要了解的是,轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。

  • 偏向锁获取:

    • 访问Mark Word中偏向锁的标识位(1bit)是否设置成1,锁标志位(2bit)是否为01——确认为可偏向状态;
    • 如果为可偏向状态,则偏向锁线程ID是否指向当前线程,如果是,执行相关同步代码;
    • 如果偏向锁线程ID并未指向当前线程,因为偏向锁不会主动释放(一直会偏向),所以当前线程可以看到对象是偏向状态以及拥有该对象锁的线程,这时表明在这个对象上已经存在竞争了,检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象变为无锁状态,然后重新CAS操作偏向新的线程,然后执行同步代码块;如果原来的线程依然存活,由偏向锁升级为轻量级锁。
  • 偏向锁升级:

    • 偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁(一直绑定唯一线程,即一直存在偏向);
    • 如果出现竞争时持有偏向锁的对象存活,则升级为轻量级锁,拷贝对象头中的Mark Word到该线程的栈帧中的锁记录(lock record)里,让lock record的指针指向锁对象头中的Mark Word,再让Mark Word指向指向lock record,唤醒线程继续执行原来线程的同步代码,此后线程间通过CAS操作竞争锁,竞争失败执行自旋操作继续竞争锁;
  • 轻量级锁升级:

    ——a线程获得锁,会在a线程的栈帧里创建lock record(锁记录),让lock record的指针指向锁对象的对象头中的mark word.再让mark word 指向lock record.这就是获取了锁。如果b线程在锁竞争时,发现锁已经被a线程占用,此时说明轻量级锁不再有效,需要进行膨胀为重量级锁。

  • 锁自旋——为了减少线程用户态到核心态之间的切换,引入了重量级锁竞争自旋的机制;

你可能感兴趣的:(八股文,java,多线程,jvm)