多线程
synchronized关键字的底层原理-进阶
❒ Monitor实现的锁属于重量级锁,你了解过锁升级吗? ✔ Monitor实现的锁属于重量级锁,里面涉及到了用户态(程序)和内核态(jvm)的切换、进程的上下文切换,成本较高,性能比较低。 ✔ 在JDK1.6引入了两种新型锁机制:偏向锁和轻量级锁,它们的引入是为了解决在没有多线程竞争或基本没有竞争的场景下因使用传统锁机制带来的性能开销问题。 ❒ 对象的内存结构 在HotSpot虚拟机中,对象在内存中存储的布局可分为3块区域:对象头(Header)、实例数据(instance Data)和对齐填充 ✔ MarkWord 对象头存放锁信息 ✔ Klass Word 描述对象实例的具体类型 ✔ 成员变量 ✔ 如果(对象头 + 实例变量)不是8的整数倍,则通过对齐填充补齐(无意义) ❒ Monitor重量级锁 ✔ 每个java 对象都可以关联一个Monitor对象,如果使用synchronized给对象上锁(重量级)之后,该对象头的Mark Word中就被设置指向Monitor对象的指针。 ❒ 轻量级锁 ✔ 在很多的情况下,在Java程序运行时,同步块中的代码都是不存在竞争的,不同的线程交替的执行同步块中的代码。这种情况下,用重量级锁是没必要的。因此IVM引入了轻量级锁的概念。 ❒ 偏向锁 ✔ 轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行CAS操作。Java6 中引入了偏向锁来做进一步优化:只有第一次使用CAS将线程ID设置到对象的Mark Word头,之后发现这个线程ID是自己的就表示没有竞争,不用重新CAS。以后只要不发生竞争,这个对象就归该线程所有。
Monitor实现的锁属于重量级锁,你了解过锁升级吗? ❒ Java中的synchronized有偏向锁、自旋锁、重量级锁三种形式,分别对应了锁只被一个线程持有、不同线程交替持有锁、多线程竞争锁三种情况。 ✔ 首先默认的无锁状态,当我们加锁以后,可能并没有多个线程去竞争锁,此时我们可以默认为只有一个线程要获取锁,即偏向锁,当锁转为偏向锁以后,被偏向的线程在获取锁的时候就不需要竞争,可以直接执行。 ✔ 当确实存在少量线程竞争锁的情况时,偏向锁显然不能再继续使用了,但是如果直接调用重量级锁在轻量锁竞争的情况下并不划算,因为竞争压力不大,所以往往需要频繁的阻塞和唤醒线程,这个过程需要调用操作系统的函数去切换 CPU 状态从用户态转为核心态。因此,可以直接令等待的线程自旋,避免频繁的阻塞唤醒。 ✔ 当竞争加大时,线程往往要等待比较长的时间才能获得锁,此时在等待期间保持自旋会白白占用 CPU 时间,此时就需要升级为重量级锁,即 Monitor 锁,JVM 通过指令调用操作系统函数阻塞和唤醒线程。 ❒ 重量级锁 ✔ 底层使用的Monitor实现,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高性能比较低。 ❒ 轻量级锁 ✔ 线程加锁的时间是错开的(也就是没有竞争),可以使用轻量级锁来优化。轻量级修改了对象头的锁标志,相对重量级锁性能提升很多。每次修改都是CAS操作,保证原子性。 ❒ 偏向锁 ✔ 一段很长的时间内都只被一个线程使用锁,可以使用了偏向锁,在第一次获得锁时,会有一个CAS操作,之后该线程再获取锁,只需要判断mark word中是否是自己的线程id即可,而不是开销相对较大的CAS命令。