synchronized分析
使用方法
1、修饰方法
public synchronized void test(){
}
2、修饰对象(锁定的为什么一定高要是对象?)
public void test2(){
//Todo
synchronized (this){
}
//todo
}
//由于是修饰对象,syn的范围由所修饰对象的生命周期来
锁是如何存储的
在之前的一篇ReentrantLock的分析中,可以看到,使用了一个线程对象来存错当前持有锁的线程,和一个state的锁标识。
这里也就可以推测,synchronized和lock应该也相似,也有这2个属性。如下:
线程a -> 对象o <- 线程b
(持有锁的线程、
锁的标识)
也就是说,在对象头中会存储,一个获锁的线程,和已经被强占锁的标识。
关于对象头,根据jvm中的介绍,一个对象在内存中的存错可以分为如下几个部分。
对象 |
---|
对象标记 |
类元信息 |
实例数据 |
[对其填充] |
对象头=对象标记+类元信息
下面为hotspot的一部分代码
enum { age_bits = 4,
lock_bits = 2,
biased_lock_bits = 1,
max_hash_bits = BitsPerWord - age_bits - lock_bits - biased_lock_bits,
hash_bits = max_hash_bits > 31 ? 31 : max_hash_bits,
cms_bits = LP64_ONLY(1) NOT_LP64(0),
epoch_bits = 2
};
可以看到有hashcode,分代年龄age,biased locl(偏向锁标识)等等.于是我们可以得到如下的表格(根据32位的hotspot)
锁状态 | 25bit | 4bit | 1bit(是否偏向锁) | 2bit(锁标志位) |
---|---|---|---|---|
无锁 | 对象的hashcode | 分代年龄 | 0 | 01 |
偏向锁 | 线程ID(23bit) + epoch(2bit) | 分代年龄 | 1 | 01 |
轻量级锁 | 指向栈中锁记录的指针(30bit) | 00 | ||
重量级锁 | 指向重量级锁记录的指针(30bit) | 10 | ||
gc标记 | 空(30bit) | 11 |
锁的状态
在1.6以后,synchronized做了一个优化(无锁->偏向锁->轻量级锁->重量级锁)也就是锁的升级,随着升级,性能消耗也就越大。
- 偏向锁:根据数据统计显示,大部分情况下,锁不存在竞争,都是由同一个线程获得。所以利用了23位来记录线程的ID, 所以当线程再次访问时,不需要获取锁,所以性能很快等于无锁的状态。
- 轻量级锁:当线程进行竞争锁的时候,会撤销偏向锁,升级为轻量级锁。轻量级为cas+自旋的方式获得。其中,jvm会记录一个lockrecord来记录锁的空间,在把对象头中的mark word 替换为指向锁记录的指针。再通过cas的方式去修改指向当前线程锁记录的指针。如果多次失败没有成功,则升级到重量级锁。
- 重量级锁:monitor 对象,获得一个objectmonitor。重量级锁的实现,都在这个对象中,一样也是通过cas去修改实现,但是失败会挂起线程。