synchronized中的类锁和对象锁
synchronized的锁是基于对象实现的
synchronized一般修饰同步代码块或者同步方法
修饰同步方法时:
如果修饰的是静态方法,那么使用的就是类锁
如果修饰的是普通方法,那么使用的就是对象锁
修饰同步代码块时:
同步代码块中的()中的对象就是锁对象
synchronized的优化
优化背景
在Jdk1.5,有大牛发明了ReentrantLock,性能比synchronized好很多,所以Jdk团队在Jdk1.6对synchronized做了优化
优化方式
锁消除:当同步代码中没有对共享变量进行操作时,说明加锁和没加锁效果相同。此时即使使用了synchronized,效果和没加锁一样
锁膨胀:当锁资源被频繁的获取和释放时,触发锁膨胀,扩大锁的范围
java
public class test {
public static void main(String[] args) {
for(int i=0; i<999999; i++){
synchronized (test.class) {
}
}
}
}
//上述代码效果在锁膨胀作用下,与下述代码相同
public class test {
public static void main(String[] args) {
synchronized (test.class){
for(int i=0; i<999999; i++){
}
}
}
}
锁升级:线程在获取ReentrantLock锁时,先基于乐观锁的CAS尝试获取锁,获取不到才会挂起。但是synchronized是获取不到锁资源就立即挂起线程
synchronized在Jdk1.6做了锁升级优化
- 无锁:还没有线程来获取锁资源
- 偏向锁:只有一个线程在频繁获取和释放锁资源,此时线程过来,只需要判断锁指向的线程是否是此线程即可
- 如果是,继续执行
- 如果不是,基于CAS的方式,尝试将锁指向当前线程。如果失败,触发锁升级,升级到轻量级锁
- 轻量级锁:采用自旋的方式基于CAS获取锁资源,自旋时间是由JVM自己决定的
- 如果成功获取,继续执行
- 如果自旋一定时间还是没有获取到,触发锁升级,升级到重量级锁
- 重量级锁:最传统的synchronized方式,线程没有获取到锁就直接挂起
synchronized的实现原理
synchronized是基于对象实现的
对象在堆内存存储的信息包括:对象头、属性数据、类对象指针、填充对齐。synchronized锁信息就存储在对象头中
对象头(MarkWork)展开后如下:

synchronized锁升级
java进程启动后,所有对象头中存储的是无锁信息,过了5秒后,所有对象头中的无锁信息会变成匿名偏向锁信息。这个过程叫做偏向锁延迟。并且开启偏向锁了,就不会再出现无锁状态。
匿名偏向锁:对象头中存储的信息和偏向锁相同,只是指向的线程信息是空的
偏向锁撤销:偏向锁在升级成轻量级锁时,需要撤销偏向锁,撤销动作需要等到安全点。在明知道会有并发情况,就选择不开启偏向锁,或者设置偏向锁延迟开启
偏向锁延迟现象背景:在java进程启动时,需要加载大量的类,在类加载过程中使用了synchronized,为了避免偏向锁撤销现象频繁发生,所以java进程启动后的5秒钟后,才会使用偏向锁

- 在偏向锁和轻量级锁时,会在线程栈开辟空间存储Lock Record
- Lock Record中存储了对象的地址信息和Mark Work信息
- 偏向锁的对象头指向栈空间的Lock Record,指向哪个线程的栈,锁就被哪个线程占有
- 在重量级锁时,由C++实现的ObjectMonitor存储了MarkWork信息和排队的线程信息

重量级锁的ObjectMonitor
重量级锁的ObjectMonitor是C语言实现的,可以在以下网址中找到其实现
https://hg.openjdk.org/jdk8u/jdk8u-dev/hotspot/file/69087d08d473/src/share/vm/runtime
ObjectMonitor中的重要结构体:
ObjectMonitor:
cpp
ObjectMonitor() {
_header = NULL; // header存储着MarkWork
_count = 0; // 竞争锁的线程个数
_waiters = 0, // wait状态的线程个数
_recursions = 0; // 表示当前synchronized锁重入的次数
_object = NULL;
_owner = NULL; // 持有锁的线程
_WaitSet = NULL; // 保存wait状态的线程信息,双向链表结构
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; // 获取锁资源失败后,线程存放的地方,单向链表结构
FreeNext = NULL ;
_EntryList = NULL ; // _cxq以及被唤醒的_WaitSet中的线程,在一定机制下,会放到_EntryList中
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
ObjectMonitor中的重要方法:
TryLock:
cpp
int ObjectMonitor::TryLock (Thread * Self) {
for (;;) {
// 拿到持有锁的线程
void * own = _owner ;
// 如果有线程持有锁,返回
if (own != NULL) return 0 ;
// 说明没有线程持有锁,own是null,cmpxchg指令是底层的CAS实现
if (Atomic::cmpxchg_ptr (Self, &_owner, NULL) == NULL) {
// 成功获取锁资源
return 1 ;
}
// 这里其实重试操作没有什么意义,直接返回-1
if (true) return -1 ;
}
}
try_enter:
cpp
bool ObjectMonitor::try_enter(Thread* THREAD) {
// 在判断_owner是否是当前线程
if (THREAD != _owner) {
// 判断当前持有锁的线程是否是当前线程
if (THREAD->is_lock_owned ((address)_owner)) {
assert(_recursions == 0, "internal state error");
_owner = THREAD ;
_recursions = 1 ;
OwnerIsThread = 1 ;
return true;
}
// CAS操作,尝试获取锁资源
if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
// 没有拿到锁资源,告辞
return false;
}
// 拿到锁资源
return true;
} else {
// 将_recursions+1,代表锁重入操作
_recursions++;
return true;
}
}