Java实例对象对包括三部分:对象头、对象体和对齐字节
对象头
Mark Word
偏向锁(101)
1.在jvm没有偏向延迟的情况下,对象进行第一次加锁时理论上会去执行偏向锁的加锁流程,首先会去判断当前线程和markwords中存放是线程id是否是同一个线程。如果是则证明加锁成功,执行下面的代码。如果不是则判断偏向标识0表示不可偏向,1表示可偏向,如果判断偏向标识是1,就会用cas去改变markwords中的线程id,能改变成功则获取到锁。如果判断是不可偏向的锁就会膨胀。
2.而能否偏向取决于有没有的调用object.hashcode和system.itdentityHashCode()方法计算哈希码,一旦对象生成了哈希码,那么就会存放到markwords中,此时偏向标识为0,表示不可偏向,因为计算了哈希码了之后该对象头无法存放线程id了。
3.但是如果对象调用的是重写了hashcode方法,那么哈希码也不会存放到markwords中。
4.如果一个对象正处于偏向锁状态,并且需要计算去hashcode值的话,那么偏向锁就会被撤销并且锁会膨胀
缺点
轻量锁(00)
当线程a持有了偏向锁,这时线程b在开执行,这时会对比markwords中的线程id是否是b,这里很显然不是,那么拿锁失败后,证明这里有线程竞争。这时会执行偏向锁的撤销流程。
持有偏向锁的线程a执行到安全点之后暂停该线程,这时会检查该线程状态,如果该线程活动已经结束或者已经退出同步代码块,则设置成无锁状态,重新执行偏向锁的流程。如果没有退出同步代码块,则升级为轻量锁,为原持有偏向锁的线程在栈上分配锁记录,拷贝对象头中markwords到原有偏向锁的锁记录中,原持有偏向锁的线程获取轻量锁,markworkds中存放指向栈中锁记录的指针。而线程b也是一样在当前栈中分配锁记录,拷贝记录,然后进行cas的加锁流程,如果加锁成功则执行同步块中代码,加锁失败则自旋再次加锁,当到达一定次数后还没有获取到锁就会升级成重量锁,防止cpu空转。
重量锁(10)
当一个线程获取到锁之后,其余线程全部都处于阻塞状态。是通过对象内部的monitor (监视器)来实现的,而这个操作线程需要从用户态切换到内核态,切换成本很高。monitor (监视器)中维护着两个队列entrylist和waitset,entrylist记录着锁竞争没有获取到锁的线程,waitset中记录着调用wait()方法而阻塞的线程。线程被notify()方法唤醒后进入entrylist中排队。
锁优化
锁消除
jvm对锁优化的一种,对运行时对上下文进行扫描,去除不可能存在资源竞争的锁。比如说给一个方法里的对象加锁,在运行时会把锁消除,因为方法里的局部变量是栈私有的,因此该对象不可能是共享资源,所以jvm会消除该对象内部的锁
锁粗化
原则上,我们使用同步锁,同步块的范围尽可能控制的越小越好,比如stringbuffer,他本身是线程安全的,因为他的关键方法被synchronize修饰过,而如果在一个循环里对stringbuffer对象重复使用append方法,那么就会出现重复对这个对象加锁,也会很损耗性能的。这是jvm就会把锁粗化到循环体的外面,避免重复加锁,使用这段操作只需要加一次锁