这篇文章是结合网上的多篇博客以及jvm虚拟机这本书写的,仅供自己复习用
synchronized的三种使用方法
- 修饰实例方法,进入代码同步块之前要先拿到对象的锁
- 修饰static方法,进入代码同步块之前要先拿到该类的锁
- 修饰代码块,指定要锁住的对象,进入同步块要拿到指定对象的锁
java对象头
在java对象内存中可分为三块
- 对象头
- 实例变量
- 填充数据
对象头是用来存储对象的hashcode,持有锁,gc年代等信息。
实例变量用来存储对象的属性方法以及父类的信息,如果是数组还包括数组长度。按四字节对其
填充数据紧紧是用来字节对齐的。
而对象持有锁的关键就在于对象头,对象头的结构如下
| 锁状态 | 25bit | 4bit | 1bit | 2bit |
|---|---|---|---|---|
| 是 | 对象hashcode | 对象分代年龄 | 是否是偏向锁 | 锁标志位(重量锁标志位为10) |
moniter
分析synchronized对象锁时,锁标识为10时指向的是moniter对象的启示地址。每一个对象都有与之对应的moniter,moniter与对象的关系有很多种方式,但当一个moniter被某个线程持有了,moniter就会处于锁定状态
ObjectMonitor() {
_header = NULL;
_count = 0; //记录个数
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL; //处于wait状态的线程,会被加入到_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; //处于等待锁block状态的线程,会被加入到该列表
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
当多个线程同时访问一个对象时,首先会进入entrylist,当线程出来之后会拿到对象,此时_owner会变为线程的名字,count+1,如果调用wait()方法,会进入waitset集合中,会释放当前的moniter,count-1,_owner重置,同时该线程进入 WaitSet集合中等待被唤醒。
若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。如下图所示
java虚拟机对锁对象的优化
偏向锁
偏向锁适用于锁竞争不强的场合。偏向锁的核心思想是,如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。
对于锁竞争不强的场景,偏向锁是合适的,但如果锁竞争强偏向锁可能会是负优化
轻量锁
倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的),此时Mark Word 的结构也变为轻量级锁的结构。轻量级锁能够提升程序性能的依据是“对绝大部分的锁,在整个同步周期内都不存在竞争”,注意这是经验数据。需要了解的是,轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。
自旋锁
如果轻量锁失效,还有种拯救方式叫自旋锁。
当前的线程可以获得锁,因此虚拟机会让当前想要获取锁的线程做几个空循环(这也是称为自旋的原因),一般不会太久,可能是50个循环或100循环,在经过若干次循环后,如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起,这就是自旋锁的优化方式,这种方式确实也是可以提升效率的。最后没办法也就只能升级为重量级锁了。
锁消失
java虚拟机在编译时会上下文扫描,如果发现锁没有必要的时候会执行锁消失,通过这种方法去消除没有必要的锁
如下StringBuffer的append是一个同步方法,但是在add方法中的StringBuffer属于一个局部变量,并且不会被其他线程所使用,因此StringBuffer不可能存在共享资源竞争的情景,JVM会自动将其锁消除。
public class StringBufferRemoveSync {
public void add(String str1, String str2) {
//StringBuffer是线程安全,由于sb只会在append方法中使用,不可能被其他线程引用
//因此sb属于不可能共享的资源,JVM会自动消除内部的锁
StringBuffer sb = new StringBuffer();
sb.append(str1).append(str2);
}
public static void main(String[] args) {
StringBufferRemoveSync rmsync = new StringBufferRemoveSync();
for (int i = 0; i < 10000000; i++) {
rmsync.add("abc", "123");
}
}
}