提到java锁机制,就得先弄清java对象结构
对象头的三个字段:
Mark Word
用于存储自身运行时的数据,例如GC标志位、哈希码、锁状态
Class Pointer
存放方法区Class对象地址,通过这个指针确定这个对象是哪个类的实例
Array Length
非必须,记录数组长度(如果是数组的话)
在32位JVM虚拟机中,Mark Word和Class Pointer这两部分都是32位的;在64位JVM虚拟机中,Mark Word和Class Pointer这两部分都是64位的。而我们需要重点理解的时mark word , 因为它跟synchronized的底层原理有关。
Mark Word
Synchronized
底层原理是生成monitorenter 和 monitorexit指令来进行线程同步,依赖于操作系统的Mutex Lock来实现的,所以每当挂起或唤醒一个线程都要切换到操作系统的内核态,性能消耗大,效率低。从JAVA6开始就引入了锁升级的概念。
对象锁的四种状态
无锁:没有对资源进行操作系统级别的锁定(Mutex Lock)
偏向锁:我们想在用户态就把这个资源的事务完成,无需切换到内核态。最好让对象能够认识这个线程,只要是这个线程来尝试获取,对象就直接把锁交出去。在对象头中的倒数第三bit 有个是否是偏向锁的标志位,如果是1,那么就去读Mark Word前23个bit,获取线程ID,通过线程ID来确认想要获得对象锁的线程是不是“偏向”的线程(有点像MVCC去获取up_limit_id 对比 transaction id的感觉了)
假如对象发现目前不止有一个线程,而有多个线程在竞争锁,那么偏向锁会升级轻量级锁
轻量级锁:升级到轻量级锁的时候,Mark Word的前30bit都用来表示指向栈中锁记录的指针。当一个线程过来要获得某个对象的锁的时候,假如看到锁标志位是00,那么就知道是轻量级锁,这个时候线程会在自己虚拟机栈开辟一块空间,称为”Lock Record“。
这个Lock Record存放了一个Mark Word的副本,以及Owner指针。然后线程就开始通过CAS去获取锁(自旋,默认10次吧好像是),一旦获得,将会复制这个对象的Mark Word到虚拟机栈的Lock Record中,并将之前的Owner指针指向该对象锁。然后Mark Word前30bit的指针将会指向这个Lock Record。这样就完成了线程和对象锁的绑定。
如果这时候有其他线程来了,想获取这个对象的锁。那他们就自旋等待。当然这个自选等待有区别与操作系统的阻塞,它是在用户态进行的,效率相比起来会高一些。一旦自选等待的线程数超过一个,那么轻量级锁就会升级为重量级锁。
重量级锁:
那就是大的来了。也就是最严格的同步控制。