synchronized详解
了解synchronized的原理,首先要了解一下对象在jvm内存中的分布:1.实例数据存放类的属性数据信息,包括父类属性信息,如果是数组的实例部分,还存放数组的长度,这部分内存按4个字节对齐。2.对齐填充数据由于虚拟机要求,对象起始地址必须是8字节的整数倍,也就是对象必须是8字节的整数倍,所以不是8字节的整数倍,则需要在这里进行补齐。3.对象头Mark Word(标记...
了解synchronized的原理,首先要了解一下对象在jvm内存中的分布:
1.实例数据
存放类的属性数据信息,包括父类属性信息,如果是数组的实例部分,还存放数组的长度,这部分内存按4个字节对齐。
2.对齐填充数据
由于虚拟机要求,对象起始地址必须是8字节的整数倍,也就是对象必须是8字节的整数倍,所以不是8字节的整数倍,则需要在这里进行补齐。
3.对象头
Mark Word(标记字段):用于存储对象自身运行时的数据。如:哈希码(hashcode)、分代年龄(gc信息)、锁标志位(锁信息)、偏向线程ID、偏向时间戳等信息,Java对象头一般占有两个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit),但是如果对象是数组类型,则需要四个机器码,因为JVM虚拟机可以通过Java对象的元数据信息确定Java对象的大小,但是无法从数组的元数据来确认数组的大小,所以用一块来记录数组长度。它是实现轻量级锁和偏向锁的关键。
Klass Pointer(类型指针):是对象指向类元素的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
如果是一个Integer对象,那么它比int占用内存大多少?
static class Test {
int a;
}
在32位系统中:
标记字段Mark Word占4字节,Klass Point占4字节,数据本身占4字节,共12字节,不是8字节的倍数,需补齐,需要4字节,故共使用16字节的内存,是int类型的4倍。
在64位系统中,Mark Word占8字节,Klass Point占8字节,如果开启指针压缩,Klass Point占4字节,那么需要
8(Mark Word)+4(Klass Point)+4(实例数据)=16字节,此时则不需要填充数据;
Monitor:监视器
它是一个同步机制,Java中所有的对象都会有一个monitor,就跟一切皆对象一样,一切对象皆有monitor;Monitor 是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。每一个被锁住的对象都会和一个monitor关联(对象头的MarkWord中的LockWord指向monitor的起始地址),同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。
monitor其实就像是一个房子,同一时间只能允许一个线程进入到房间内,没有进入的线程,则会在入口队列(Entry set)中等待进入;如果当一个线程获取到锁的时候,由于某些原因,调用了Object的wait方法,则此时的线程则进入到条件等待队列(wait set)堵塞进行等待。
synchronize用的锁是在Java对象头里的,它是JVM层面的:
public class Test{
public synchronized void getName(){
}
public void test(){
synchronized(this){
}
}
}
反编译之后
其中同步代码块的入口是monitorenter,出口是monitorexit;
同步方法,则在修饰符上的ACC_SYNCHRONIZED实现的。
通过同步代码快,可以看出:
synchronized是通过monitor监视器来实现的同步:
1.当执行monitorenter的时候,如果monitor进入数为0,则该线程进入到Monitor,然后将进入数设置为1,该线程即位Monitor的所有者;
2.如果线程已经占有Monitor,只是重新进入,则进入数加1;
3.如果线程已经占用,则该线程进入堵塞等待状态,直到进入数为0,再重新尝试获取Monitor所有权;
其记录信息:
1)hash:对象的hashCode
2)age:对象的分代年龄
3)biased_lock:是否是偏向锁
4)lock:锁标记
5)javaThread:如果是偏向锁,记录线程ID
synchronized锁在JDK1.6之后,优化成了四个阶段:
1.无锁
无锁状态就是指没有对资源进行锁定,所有线程都能访问并修改同一个资源,但是同时只能有一个线程修改成功。
2.偏向锁
指同步代码块一直被一个线程访问时,即不存在多个线程竞争修改资源的情况下,那么该线程的后续访问时便会自动获取锁,只需要几步简单的操作,不需要CAS操作就可以实现,从而降低获取锁带来的开销,即提高了性能。偏向锁,就是偏向第一个线程,该线程是不会主动释放锁的,只有当其他线程尝试竞争偏向锁时才会释放。
加锁过程:
2.1:判断对象是否是偏向状态,即Mark word中,偏向标志(biased_lock)为1,锁标记(lock)为01,javaThread是否为0。如果偏向标志为0,javaThread为0,此时也叫匿名偏向。此时,则会调用CAS指令,将Mark Word中的javaThread改成当前线程的ID,biased_lock设置为1。如果成功,则获取了偏向锁。
2.2:如果不是出在匿名偏向锁状态下,则会判断持有的偏向锁线程是否还存活,不存活,则当前线程可以获取到偏向锁。
2.3:若此时有线程进来想获取偏向锁,但发现被占用,则此时就会升级为轻量级锁。
撤销过程:
需要等待全局安全点,代表了一个状态,在该状态下所有线程都是暂停的。先暂停拥有偏向锁的线程,判断锁对象是否处于存活状态,不存活,则对对象头设置成无锁状态。
3.轻量级锁
是指当线程A获取到偏向锁后,此时B线程也尝试获取偏向锁,就会发生偏向锁升级为轻量级锁,此时的线程B会通过自旋的形式尝试获取锁,线程不会堵塞,从而提高性能。
3.1:当有一个线程等待的时候,此时处于自旋的状态,但是当自旋超过一定次数之后,轻量级锁便会升级为重量级锁。
3.2:当有一个自旋的线程等待时,此时又来了第三个线程,那么这样轻量级锁也会升级为重量级锁。
4.重量级锁
当一个线程拥有锁后,其余线所有等待获取该锁的线程处于堵塞状态。
这里有用到了Monitor(监视器)来实现。Mark Word指向Monitor,也就是利用了它的互斥锁。
更多推荐
所有评论(0)