在javaSE1.6中对Synchronized进行了大量的优化,使一个笨重的锁变得灵活了起来。
在讲Synchronized之前,要先介绍一下java的对象头:
- 在32位的虚拟机中,java对象头使用25bit存储对象的hashCode,用4bit存储对象的分代年龄,用1bit来判断是否是偏向锁,再用2bit来存储锁的标志位。(00为轻量级锁 01为偏向锁 11为GC标记 10为重量级锁)
- 在64位的虚拟机中,无锁的时候,25bit不用,31bit用来存储hashCode,1bit用来存储cms_free,4bit存储分代年龄,1bit判断是否为偏向锁,2bit存储锁的标志位。在有锁时,前56位中(54bit存储线程ID,2bit存储Epoch)
从上面可以看出,共同点是都有3个bit给锁使用。
得出结论:java中的每一个对象都可以作为锁。
Synchronized的三种用法:
- 对于普通同步方法,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的Class对象。
- 对于同步方法块,锁是Sychronized括号里配置的对象。
在javaSE1.6中锁一共有4种状态:无锁、偏向锁、轻量级锁、重量级锁。
下面把锁转换的流程说一遍:
当一个线程访问同步代码块并获取锁的时候,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID。当该线程再次进入同步代码块时,只需判断对象头的Mark World里是否存储着指向当前线程的偏向锁。
如果成功,则表示已获得了锁,直接进入。
如果失败,则再判断该mark world中的锁标识是否为偏向锁。
如果该锁标识为偏向锁则进行偏向锁的竞争。
如果该锁标识不为偏向锁,则进行对应锁的竞争。
偏向锁的竞争及升级过程:
1.线程2来竞争锁对象
2.判断拥有偏向锁的线程1是否还存在
3.如果线程1不存在,则设置偏向锁标识为0
4.用cas操作替换锁线程ID为线程2,锁不升级,仍然为偏向锁。
5.线程1仍然存在,暂停线程1;
6.设置锁的标志位为00,偏向锁标识为0,升级为轻量级锁。
7.在线程1的栈帧中创建用于存储锁记录的空间,并将对象头中的MarkWorld复制到锁记录中。
8.继续执行线程1的代码。
9.线程2进行自旋操作获得轻量级锁。
轻量级锁升级:
1.如果线程2自旋后获取锁失败,或者在线程2自旋的时候,线程3也来获取锁,那么该锁会升级为重量级锁。当锁处于这个状态下时,其他线程试图获取锁时都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮的竞争。