一、偏向锁
偏向锁是对轻量级锁的优化,正常轻量级锁是使得obj的Mark Word指向lock record的地址,且当轻量级锁没有竞争的时,每次在同一线程内的重入仍然需要执行CAS操作(即同一个线程内的synchronized嵌套synchronized)
Java6 中引入偏向锁来进一步优化:只有第一次使用CAS将线程ID设置为到对象的Mark Word头,之后发现这个线程ID是自己的就表示没有竞争,不用重新CAS。以后只要不发生竞争,这个对象就归该线程所有。
相当于将线程的名字刻在对象中,这也是“偏向”的含义,即给对象所加的锁偏向于给某一线程使用。
例如:
static final Object obj = new Object();
public static void m1(){
synchronized(obj){
//同步块A
m2();
}
}
public static void m2(){
synchronized(obj){
//同步块B
m3();
}
}
public static void m3(){
synchronized(obj){
//同步块C
}
}
- 不使用偏向锁的情况
- 使用偏向锁的情况
第一次synchronized的时候将obj的Mark Word字段置为ThreadID,当线程内第二次synchronized的时候直接检查Mark Word中的Thread ID是否是当前线程,如果是代表当前线程已经持有锁,即不用再执行CAS操作,节省时间。
二、偏向状态
一个对象创建时:
- 如果开启了偏向锁(默认开启),那么对象创建后,markword的值为0x05即最后3位为101,这时它的thread、epoch、age都为0
- 偏向锁默认是延迟的,不会在程序启动时默认生效,如果想避免延迟,可以加VM参数-XX:BiasedLockingStartupDelay=0来禁用延迟
- 如果没有开启偏向锁,那么对象创建后,markword值为0x01即最后3位为001,这时它的hashcode、age都为0,第一次用到hashcode时才会赋值。
注意:
- 处于偏向锁的对象解锁后,线程id仍存储于对象头中,所以偏向锁适用于单线程没有竞争的情况,偏向锁无法适用于多线程竞争锁的情况。
- 如果偏向锁状态的对象调用hashcode()方法后,偏向锁直接被禁用。因为偏向锁实际上是一种非常简单的锁,只处理单线程,且其markword字段记录的是thread等信息。不像轻量级锁和重量级锁都有记录原对象hashcode等信息的位置,偏向锁如果想要记录hashcode值,只能从偏向锁转为无锁normal状态来记录hashcode值。
- 注意hashcode只有在调用hashcode()方法之后才会有,否则初始值默认
可以添加VM参数:-xx:-UseBiasedLocking禁用偏向锁,那么发现synchronized加锁后,加的是轻量级锁。
所以综上,整个synchronized语法不变,但优先级的顺序是:偏向锁(默认)->轻量级锁->重量级锁
三、撤销偏向锁
撤销-调用对象hashCode
调用了对象的hashCode,但偏向锁的对象MarkWord中存储的是线程id,如果调用hashCode会导致偏向锁被撤销
- 轻量级锁会在锁记录中记录hashCode
- 重量级锁会在Monitor中记录hashCode
在调用hashCode后使用偏向锁,记得去掉:-xx:-UseBiasedLocking
输出:
撤销-其它线程使用对象
当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁
具体流程为:线程2来执行synchronized会尝试为obj加偏向锁,但发现obj的Thread ID已经为线程1的ID值,即obj对象已经偏向线程1,这时会将偏向锁升级为轻量级锁,进入轻量级锁执行流程,当线程2退出synchronized开始解锁时,进入轻量级锁解锁流程,对象会被重新置为无锁状态。
注意升级成轻量级锁的条件是:多个线程之间没有竞争,时间上错开,这也是轻量级锁的定义。
四、批量重偏向
如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程T1的对象仍有机会重新偏向T2,重偏向会重置对象的Thread ID
当撤销偏向锁阈值超过20次后,jvm会认为偏向错误,于是会在给这些对象加锁时重新偏向至加锁线程
具体代码思路为:
在线程1中for循环30次,每次给对象一个synchronized,即加上偏向锁。
在线程2中也同样for循环30次,也每次给对象一个synchronized,正常来说线程2执行synchronized后会释放轻量级锁变为无锁状态,即撤销偏向锁,但是jvm认为线程2执行这么多次撤销操作,可以认为对象可以偏向线程2,故后续不再撤销偏向锁,而是将偏向锁加在线程2上。
五、批量重偏向
当撤销偏向锁阈值超过40次后,jvm会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的。(给jvm整emo了)
六、锁消除
加锁会对性能产生损耗,锁消除则是java自动对锁优化,例子如下:
public class SuoXiaoChu {
static int x=0;
public void a() throws Exception{
x++;
}
public void b() throws Exception{
Object o =new Object();
synchronized (o){
x++;
}
}
}
对象o没有被多线程共享,所以java会自动将synchronized()代码块去掉,所以实际显示a()方法和b()方法耗时差不多。