synchronized的实现原理与应用
synchronized的同步基础:Java中的每一个对象都可以作为锁。
static synchronizedTest instance=new synchronizedTest();
public void run() {
synchronized(instance){
//同步代码块
//*******
}
}
void synchronized method1() {} //类中的同步方法
void static synchronized method2() {} 类中静态同步方法
JVM基于进入和退出Monitor对象来实现方法同步和代码块的同步。
monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM会保证每个monitorenter必须有对应的monitorexit与之配对。每个对象都会有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。
Java对象头
1.对象头:Java对象头一般占有2个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit,在64位虚拟机中,1个机器码是8个字节,也就是64bit),但是 如果对象是数组类型,则需要3个机器码,因为JVM虚拟机可以通过Java对象的元数据信息确定Java对象的大小,但是无法从数组的元数据来确认数组的大小,所以用一块来记录数组长度。
2.实例数据:存放类的属性数据信息,包括父类的属性信息;
3.对齐填充:由于虚拟机要求 对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐;
java对象头里面的Mark Word里默认存储对象的HashCode、分代年纪和锁标志位。
对象头信息是与对象自身定义的数据无关的额外存储成本,但是考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据,它会根据对象的状态复用自己的存储空间,也就是说,Mark Word会随着程序的运行发生变化,可能变化为存储以下4种数据:
在64位虚拟机啊下,Mark Word是64bit大小的
锁的升级与优化
锁一共有四种状态: 无锁、偏向锁、轻量级锁、重量级锁。锁可以升级,但是不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。目的是为了提高获得锁和释放锁的效率。
偏向锁
因为很多时候不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低引入了偏向锁。
当一个线程访问同步块并获取锁时,会在对象头和栈帧的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。
偏向锁的撤销
偏向锁使用了一种等待竞争出现才会释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。
撤销的过程:
偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正在执行的字节码)
作用:在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥产生的性能消耗。
轻量级锁获得过程:
如果更新失败了,那么久意味着至少存在一条线程与当前线程竞争获取该对象的锁
虚拟机首先会检查对象的Mark Words是否指向当前的栈帧。
锁膨胀
如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有 竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。
static Object obj = new Object();
public static void method1() {
synchronized( obj ) {
// 同步块
}
}



