重量级锁synchronized

738 阅读4分钟

源码

源码地址:jdk8u

源码地址:OpenJDK / jdk8u / jdk8u / hotspot directory /src/share/vm/runtime/

源码地址:OpenJDK / jdk8u / jdk8u / jdk directory /src/share/native/java/lang/

CAS

compare and swap

compare and exchange

ABA问题

解决

在汇编层级上的lock + compare and exchange

AtomicXXX

如果是用AtomicXXX就可以不用上锁了,因为已经 lock comxchg 了
源码
unsafe.cpp->Atomic::cmpxchg->atomic.cpp->atomic_linux_x86.inline.hpp->Atomic::cmpxchg->_asm_ volatile(LOCK_IF_MP(%4)"comxchgl..."...);

LOCK_IF_MP保证了其原子性

对象在内存中的存储布局

来自:https://www.cnblogs.com/zhengbin/p/6490953.html

参考:Java对象在内存中的布局

参考:JVM——深入分析对象的内存布局

markword对象头:

包括两部分信息

第一部分:对象自身的运行时数据,如哈希码,GC分代年龄, 锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等,这部分数据的长度在32 位和64位的虚拟机中分别为32 bit和64 bit,官方称它为“Mark Word”。

第二部分:类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确 定这个对象是哪个类的实例。如果对象是一个java数组,那在对象头中还必须有一 块用于记录数组长度的数据。

instance data实例数据:

是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。

padding对齐填充:

对齐填充不是必然存在的。HotSpot VM的自动内存管理系统要求对象起始地址必须 是8字节的整数倍,也就是说对象的大小必须是8字节的整数倍。而对象头部分正好 是8字节的整数倍。因此,当对象实例数据部分没有对齐时,就需要通过对其补充 来补全了。

普通对象

markword-class pointer-instance data-padding

数组

markword-class pointer-length-instance data-padding

分析工具

JOL
ClassLayout.parseInstance(object).toPrintable();

给对象上锁

也就是在markword的value上进行改变

升级过程

参考马士兵老师的图片

马士兵老师的图片

偏向锁

偏向于第一次用到这个锁的线程,把自己线程的id放到mardword

java -XX:+PrintFlagsFinal -version 
java -XX:+PrintFlagsFinal -version | grep BiasedLocking

BiasedLockingStartupDelay 偏向锁会延迟一定的时间才打开,会等JVM启动完以后再启用

轻量级锁(自旋锁)

用CAS的方式修改markword,把自己的id放进去,谁抢成功了这把锁就是谁的。

Lock Record 的指针

重量级锁

C++的层面ObjectMonitor

源码地址:

objectMonitor.cpp

objectMonitor.hpp

objectMonitor.hpp

synchronized的缺点:

  • 1.当一个代码块被synchronized修饰的时候,一个线程获取到了锁,并且执行代码块,那么其他的线程需要等待正在使用的线程释放掉这个锁,那么释放锁的方法只有两种,一种是代码执行完毕自动释放,一种是发生异常以后jvm会让线程去释放锁。那么如果这个正在执行的线程遇到什么问题,比如等待I0或者调用sleep方法等等被阻塞了,无法释放锁,而这时候其他线程只能一直等待,将会特别影响效率。那么有没有一种办法让其他线程不必一直傻乎乎的等在这里吗?

  • 2.当一个文件,同时被多个线程操作时,读操作和写操作会发生冲突,写操作和写操作会发生冲突,而读操作和读操作并不会冲突,但是如果我们用synchronized的话,会导致一个线程在读的时候,其他线程想要读的话只能等待,那么有什么办法能不锁读操作吗?

  • 3.在使用synchronized时,我们无法得知线程是否成功获取到锁,那么有什么办法能知道是否获取到锁吗?

应用场景

  • 在资源竞争不是很激烈的情况下,synchronized的性能要优化于ReentrantLock。
  • 但是在资源竞争很激烈的情况下,synchronized的性能会下降几十倍,但是ReentrantLock的性能能维持常态。
  • ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)

锁状态 25位 31位 1位 4位 1位(偏向锁位) 2位(锁标志位)
无锁态 unused hashCode(如果有调用) unused 分代年龄 0 0 1
锁状态 54位 2位 1位 4位 1位(偏向锁位) 2位(锁标志位)
偏向锁 当前线程指针JavaThread* Epoch unused 分代年龄 1 0 1
锁状态 62位 2位(锁标志位)
轻量级锁(自旋锁) 指向线程中Lock Record的指针 0 1
重量级锁 指向互斥量(重量级锁)的指针 1 0
GC标记信息 CMS过程用到的标记信息 1 1