一、使用(方法,代码块)
Synchronized锁就是基于对象实现的,只不过分为class对象和其他
- this或者没有static修饰的方法,表示当前对象锁。只要用这个对象就被锁了,A线程访问S类的s1对象和B线程访问S类的S1对象,要竞争
- .clss 和 static 表示类锁。只要你用这个类就牵扯类锁,比如A线程访问S类的s1对象和B线程访问S类的S2对象,一样要竞争
- 普通对象,就表示锁了这个普通对象
- 字符串算是类锁
代码块
public static void main(String[] args) {
Object obj = new Object();
synchronized (obj) {
// ...
}
}
synchronized (this) {
// ...
}
synchronized (Test.class) {
// ...
}
方法
private static synchronized void sync () {
}
private synchronized void sync () {
}
二、优化(锁消除、锁粗化、锁升级)
jdk1.6以后,对synchronized做了锁消除,锁粗化、锁升级。要不然这个锁一直是重量级锁,就会导致没有人用了,浪费性能。用ReentrantLock不香吗?
锁消除
- 就是当synchronized包裹的代码里面,没有临界资源或者说没有跟别的线程会有竞争的时候,这个锁是不存在的,就会把锁消除
- jvm逃逸分析,这个锁对象会不会被多个线程共享,不会的话,就会把锁消除。
锁粗化
- 当一串代码需要不停的需要这个锁的时候,就会浪费资源,还不如把这个锁的范围扩大
锁升级
对象在jvm堆中的内存结构,对象头中的MarkWord如下
无锁
- 就是没有锁
偏向锁/匿名偏向锁
- 匿名偏向锁:只在对象的markword改变了锁状态,但是没有对象的地址。
- 当只有一个线程竞争这个锁的时候,就会从无锁变为偏向锁。然后再锁对象的markword中记录这个线程的ID,以后这个线程来了,就继续用啊。
- 偏向锁有一个默认时延4s。可以修改。就是程序启动后,不会立马有偏向锁,而是要等到4s后,为啥呢?
- 因为hostspot加载类的时候,ClassLoder类中,加载类是synchronized的。而偏向锁向轻量级转化的时候,需要有一个安全点(STW)才能转化。但是你是启动啊,提升启动效率。
- 默认是开启时延,且开启偏向锁的。那么这个时候,启动的时候不会立马有偏向锁。但是等时延过了之后,就不会有无锁的状态了,起码都是个匿名偏向锁。
轻量级锁
- 当有多个线程竞争同一把锁的时候,偏向锁升级为轻量级锁。竞争的线程会在栈中开一个空间Lock Record用来复制锁对象的Mark Word,用自适应自旋的方式(CAS)来竞争这个锁。只要能将锁对象头中的指针改为自己,那么就是抢锁成功了
重量级锁
- 底层是ObjectMonitor,这个玩意就是hotspot的c代码了。(有兴趣去研究一下,下面是在百度抄的,大概瞅瞅就行了)
ObjectMonitor() {
_header = NULL; // header存储着对象的MarkWord
_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;
}
TryLock
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_entry
bool ObjectMonitor::try_enter(Thread* THREAD) {
// 在判断_owner是不是当前线程
if (THREAD != _owner) {
// 判断当前持有锁的线程是否是当前线程,说明轻量级锁刚刚升级过来的情况
if (THREAD->is_lock_owned ((address)_owner)) {
_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;
}
}
enter(想方设法拿到锁资源,如果没拿到,挂起扔到_cxq单向链表中)
void ATTR ObjectMonitor::enter(TRAPS) {
// 拿到当前线程
Thread * const Self = THREAD ;
void * cur ;
// CAS走你,
cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
if (cur == NULL) {
// 拿锁成功
return ;
}
// 锁重入操作
if (cur == Self) {
// TODO-FIXME: check for integer overflow! BUGID 6557169.
_recursions ++ ;
return ;
}
//轻量级锁过来的。
if (Self->is_lock_owned ((address)cur)) {
_recursions = 1 ;
_owner = Self ;
OwnerIsThread = 1 ;
return ;
}
// 走到这了,没拿到锁资源,count++
Atomic::inc_ptr(&_count);
for (;;) {
jt->set_suspend_equivalent();
// 入队操作,进到cxq中
EnterI (THREAD) ;
if (!ExitSuspendEquivalent(jt)) break ;
_recursions = 0 ;
_succ = NULL ;
exit (false, Self) ;
jt->java_suspend_self();
}
}
// count--
Atomic::dec_ptr(&_count);
}
EnterI
for (;;) {
// 入队
node._next = nxt = _cxq ;
// CAS的方式入队。
if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;
// 重新尝试获取锁资源
if (TryLock (Self) > 0) {
assert (_succ != Self , "invariant") ;
assert (_owner == Self , "invariant") ;
assert (_Responsible != Self , "invariant") ;
return ;
}
}
锁降级
- 其实是可以降级的,因为偏向锁没有地方存hashcode,当处于偏向锁的时候,需要使用对象的hashcode,那么就要从偏向锁降级到无锁。