锁膨胀过程3
偏向锁流程图总结
场景1—第二次偏向加锁
-
两个挨着的synchronized代码块
-
第二次偏向加锁,在栈当中创建一个lock record,判断对象头里面的内容(t1id + epoch +age+101),跟自己锁记录里面的对象头内容(obj ref)是否相同,比较的是位运算计算出来的一个值
-
anticipated_bias_locking_value = (((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) & ~((uintptr_t) markOopDesc::age_mask_in_place);
-
如果这个值等于0,说明是第一次加偏向锁的线程,则直接获取偏向锁
场景2----轻量锁加锁
- t3线程来了,首先创建一个锁记录,首先关联对象头,然后在cpu里面产生一个001的对象头,如果和对象头的mark word相同,把mark word改成lock record的地址指针
场景3—轻量锁膨胀为重量锁
- t4线程来了,在t3线程判断的时候,把对象头里面的后两位改成00了,那么t3线程就会cas失败,进入if逻辑体,进入重量锁升级的逻辑
场景4—匿名偏向锁(第一次加锁)
- 匿名偏向,首先产生一个无锁的对象头,对象头里面匿名101,代码执行到匿名偏向锁逻辑时,如果是匿名偏向,首先产生一个无锁的new_header,偏向于当前线程的对象头,然后对当前对象的对象头进行cas,也就是判断当前对象有没有被别人改变,如果没有则偏向该线程
场景5—批量重偏向
- 批量重偏向,如果对同一个类对象进行了偏向锁批量撤销超过20次后,会进入源代码中偏向锁if语句中带有epoch关键字的判断语句,其中epoch会记录偏向锁撤销次数,如果正好是第20次偏向撤销,知道偏向锁过期了,会进行重偏向,没有体现批量重偏向?
- 怎么体现的批量重偏向?
- 现在持有这一类对象的锁的所有线程如果偏向t1,当对这个类进行超过20次偏向撤销后,之后再对该类进行加锁时,则直接偏向执行加锁代码的线程。
- 批量重偏向的是正在持有锁的对象
场景6—偏向撤销
- 当一个偏向锁如果撤销次数到达40的时候就认为这个对象的设计有问题;那么JVM会把这个对象所对应的类所有的对象都撤销偏向锁;并且新实例化的对象也是不可偏向的
流程图过程(bytecodeinterpreter.cpp)
进入到CASE(monitorenter)
1.获取锁对象
2.创建一个锁记录
3.让锁记录关联lockee
4.获取lockee对象头的信息
5.判断是否关闭偏向
if错误 直接升级成轻量锁
- 走轻量锁逻辑
if正确 开启偏向
-
1.线程id和mark计算判断是否是偏向自己
if 正确 获取到锁
if错误 偏向是否禁用了
- 1.获取做cas操作,把对象头设置为header,不管cas是否成功,依然会走到轻量锁逻辑
-
2.判断是否过期
if 正确 创建一个偏向自己的mark,cas将markword中保存所记录中的指针
if错误 会进入slow_enter
-
3.如果不满足,进入最后一个else
- 1创建一个偏向自己的对象头
- 2.如果cas成功,说明是匿名偏向
- 3.如果cas失败,则说明是偏向别人,去做锁撤销,然后升级为轻量锁
偏向锁源码未讲的一个逻辑分支
-
if (anticipated_bias_locking_value == 0) { // already biased towards this thread, nothing to do if (PrintBiasedLockingStatistics) { (* BiasedLocking::biased_lock_entry_count_addr())++; } success = true; } // 偏向被禁用了 else if ((anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) { // try revoke bias // 撤销偏向,注意此处是类当中的mark,而不是对象当中的mark,而类当中的mark是不可偏向的,所以直接就是变成不可偏向的了 markOop header = lockee->klass()->prototype_header(); if (hash != markOopDesc::no_hash) { header = header->copy_set_hash(hash); } if (lockee->cas_set_mark(header, mark) == mark) { if (PrintBiasedLockingStatistics) (*BiasedLocking::revoked_lock_entry_count_addr())++; } }
ObjectMoniter是synchronized中的重量锁,与ReentrantLock类似
上一步流程图失败的都会进入轻量锁逻辑
-
fast_enter
-
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) { // 有没有使用偏向锁 if (UseBiasedLocking) { if (!SafepointSynchronize::is_at_safepoint()) { // 要么撤销 要么重偏向 BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD); if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) { return; } } else { assert(!attempt_rebias, "can not rebias toward VM thread"); BiasedLocking::revoke_at_safepoint(obj); } assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now"); } // 升级为轻量锁 slow_enter(obj, lock, THREAD); }
-
slow_enter
-
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) { markOop mark = obj->mark(); assert(!mark->has_bias_pattern(), "should not see bias pattern here"); // 首先判断是不是无锁---前一步如果禁用偏向锁模式得到的是001的对象头 if (mark->is_neutral()) { // Anticipate successful CAS -- the ST of the displaced mark must // be visible <= the ST performed by the CAS. // 把无锁放到锁状态中去 lock->set_displaced_header(mark); // 进行cas判断 // 跟之前if(!success)中执行的轻量锁加锁逻辑相同 if (mark == obj()->cas_set_mark((markOop) lock, mark)) { // 轻量锁加锁成功,则return return; } // Fall through to inflate() ... } // 如果是有锁状态 并且是持有锁的线程是自己,锁的重入 else if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) { assert(lock != mark->locker(), "must not re-lock the same lock"); assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock"); // 设置一个null的对象头 lock->set_displaced_header(NULL); return; } // The object header will never be displaced to this lock, // so it does not matter what the value is, except that it // must be non-zero to avoid looking like a re-entrant lock, // and must not look locked either. // 设置一个没有使用的对象头 // lock->set_displaced_header(markOopDesc::unused_mark()); // inflate(膨胀)--去进行重量锁的膨胀 // 膨胀完执行enter(THREAD)方法,就是加入队列 // 膨胀成功时010 -- 有别的线程在执行,没有是否锁 ObjectSynchronizer::inflate(THREAD, obj(), inflate_cause_monitor_enter)->enter(THREAD); }
-
ObjectMonitor.cpp
-
void ObjectMonitor::enter(TRAPS) { // The following code is ordered to check the most common cases first // and to reduce RTS->RTO cache line upgrades on SPARC and IA32 processors. Thread * const Self = THREAD; void * cur = Atomic::cmpxchg(Self, &_owner, (void*)NULL); if (cur == NULL) { // Either ASSERT _recursions == 0 or explicitly set _recursions = 0. assert(_recursions == 0, "invariant"); assert(_owner == Self, "invariant"); return; } // 重入次数加一 if (cur == Self) { // TODO-FIXME: check for integer overflow! BUGID 6557169. _recursions++; return; } if (Self->is_lock_owned ((address)cur)) { assert(_recursions == 0, "internal state error"); _recursions = 1; // Commute owner from a thread-specific on-stack BasicLockObject address to // a full-fledged "Thread *". _owner = Self; return; } // We've encountered genuine contention. assert(Self->_Stalled == 0, "invariant"); Self->_Stalled = intptr_t(this); // Try one round of spinning *before* enqueueing Self // and before going through the awkward and expensive state // transitions. The following spin is strictly optional ... // Note that if we acquire the monitor from an initial spin // we forgo posting JVMTI events and firing DTRACE probes. if (TrySpin(Self) > 0) { assert(_owner == Self, "invariant"); assert(_recursions == 0, "invariant"); assert(((oop)(object()))->mark() == markOopDesc::encode(this), "invariant"); Self->_Stalled = 0; return; } assert(_owner != Self, "invariant"); assert(_succ != Self, "invariant"); assert(Self->is_Java_thread(), "invariant"); JavaThread * jt = (JavaThread *) Self; assert(!SafepointSynchronize::is_at_safepoint(), "invariant"); assert(jt->thread_state() != _thread_blocked, "invariant"); assert(this->object() != NULL, "invariant"); assert(_count >= 0, "invariant"); // Prevent deflation at STW-time. See deflate_idle_monitors() and is_busy(). // Ensure the object-monitor relationship remains stable while there's contention. Atomic::inc(&_count); JFR_ONLY(JfrConditionalFlushWithStacktrace<EventJavaMonitorEnter> flush(jt);) EventJavaMonitorEnter event; if (event.should_commit()) { event.set_monitorClass(((oop)this->object())->klass()); event.set_address((uintptr_t)(this->object_addr())); } { // Change java thread status to indicate blocked on monitor enter. JavaThreadBlockedOnMonitorEnterState jtbmes(jt, this); Self->set_current_pending_monitor(this); DTRACE_MONITOR_PROBE(contended__enter, this, object(), jt); if (JvmtiExport::should_post_monitor_contended_enter()) { JvmtiExport::post_monitor_contended_enter(jt, this); // The current thread does not yet own the monitor and does not // yet appear on any queues that would get it made the successor. // This means that the JVMTI_EVENT_MONITOR_CONTENDED_ENTER event // handler cannot accidentally consume an unpark() meant for the // ParkEvent associated with this ObjectMonitor. } OSThreadContendState osts(Self->osthread()); ThreadBlockInVM tbivm(jt); // TODO-FIXME: change the following for(;;) loop to straight-line code. for (;;) { jt->set_suspend_equivalent(); // cleared by handle_special_suspend_equivalent_condition() // or java_suspend_self() // enterI方法 EnterI(THREAD); if (!ExitSuspendEquivalent(jt)) break; // We have acquired the contended monitor, but while we were // waiting another thread suspended us. We don't want to enter // the monitor while suspended because that would surprise the // thread that suspended us. // _recursions = 0; _succ = NULL; exit(false, Self); jt->java_suspend_self(); } Self->set_current_pending_monitor(NULL); // We cleared the pending monitor info since we've just gotten past // the enter-check-for-suspend dance and we now own the monitor free // and clear, i.e., it is no longer pending. The ThreadBlockInVM // destructor can go to a safepoint at the end of this block. If we // do a thread dump during that safepoint, then this thread will show // as having "-locked" the monitor, but the OS and java.lang.Thread // states will still report that the thread is blocked trying to // acquire it. } Atomic::dec(&_count); assert(_count >= 0, "invariant"); Self->_Stalled = 0; // Must either set _recursions = 0 or ASSERT _recursions == 0. assert(_recursions == 0, "invariant"); assert(_owner == Self, "invariant"); assert(_succ != Self, "invariant"); assert(((oop)(object()))->mark() == markOopDesc::encode(this), "invariant"); // The thread -- now the owner -- is back in vm mode. // Report the glorious news via TI,DTrace and jvmstat. // The probe effect is non-trivial. All the reportage occurs // while we hold the monitor, increasing the length of the critical // section. Amdahl's parallel speedup law comes vividly into play. // // Another option might be to aggregate the events (thread local or // per-monitor aggregation) and defer reporting until a more opportune // time -- such as next time some thread encounters contention but has // yet to acquire the lock. While spinning that thread could // spinning we could increment JVMStat counters, etc. DTRACE_MONITOR_PROBE(contended__entered, this, object(), jt); if (JvmtiExport::should_post_monitor_contended_entered()) { JvmtiExport::post_monitor_contended_entered(jt, this); // The current thread already owns the monitor and is not going to // call park() for the remainder of the monitor enter protocol. So // it doesn't matter if the JVMTI_EVENT_MONITOR_CONTENDED_ENTERED // event handler consumed an unpark() issued by the thread that // just exited the monitor. } if (event.should_commit()) { event.set_previousOwner((uintptr_t)_previous_owner_tid); event.commit(); } OM_PERFDATA_OP(ContendedLockAttempts, inc()); }
-
进入enterI()方法
-
关键方法是Self->ParkEvent0->Park();
重量锁进入enter简单理解
-
膨胀成功之后—010
-
1.cas加锁,有可能在我膨胀之后,别人刚好释放锁 我就去加锁 为了不去排队 也就是不sleep
-
2.cas失败—自旋—2到3次,也就是cas加锁,如果再失败就会进入队列,进入队列后还有可能自旋,如果再失败,就会睡眠
重量锁的工作模式—ObjectMonitor
-
-
跟reentrantlock基本差不多,就是多了一个waitset
-
重量锁mark word前62位指向ObjectMonitor重量锁的指针,后两位是10
waitset
- waitset是已经拿过锁的,调用了wait方法的,entrylist是等待拿synchronized锁的,waitset中被叫醒的线程会重新进入entrylist重新排队
wait、notify
- 持有锁的线程发现条件不满足,调用 wait,即可进入 WaitSet 变为 WAITING 状态
BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片
BLOCKED 线程会在 持有锁的线程释放锁时自动唤醒
WAITING 线程会在 持有锁的线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味者立刻获得锁仍需
进入EntryList 重新竞争
和sleep的区别
- 相同:线程的状态相同;都是阻塞状态
- 区别:
1、wait是Object的方法,object的9个方法之一;任何对象都可以直接调用;sleep是Thread的静态方法
2、wait必须配合 synchronized 关键字一起使用;如果一个对象没有获取到锁直接调用wait会异常;sleep则不需要
3、wait可以通过notify主动唤醒;sleep只能通过打断主动叫醒
4、wait会释放锁、sleep在阻塞的阶段是不会释放锁的
深入理解wait 和notify sleep
-
public class TestWait1 { static final Object key = new Object(); static boolean isPrettyGril = false; // 有没有女人 /** * 多线程情况下 假设某个线程拿到锁了 但是他需要满足某个条件才能执行 * 如果用sleep 会导致在没有满足条件的情况下:我一直持有锁,别的线程 * 拿不到锁 */ public static void main(String[] args) throws InterruptedException { new Thread(() -> { synchronized (key) { log.debug("有没有女人[{}]", isPrettyGril); if (!isPrettyGril) { log.debug("没有女人!等女人,等5秒;别人也不能干活"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("等了5秒 有没有女人?[{}]", isPrettyGril); if (isPrettyGril) { log.debug("------男女搭配干活不累;写完了代码"); }else{ log.debug("------下班回家----"); } } }, "jack").start(); for (int i = 0; i < 10; i++) { new Thread(() -> { synchronized (key) { log.debug("我们10个屌丝工作了"); } }, "其它人").start(); } // 要想程序执行,需要下面不加synchronized,这样才能变更isPrettyGirl这个值,睡眠5秒后,执行打印代码 // 如果加了synchronized,这个线程也一直拿不到锁 Thread.sleep(1000); new Thread(() -> { synchronized (key) { isPrettyGril = true; log.debug("桥本有菜来了"); } }, "boss").start(); } }
问题:sleep不会释放锁 导致其他线程也不会执行
-
import lombok.extern.slf4j.Slf4j; @Slf4j(topic = "enjoy") public class TestWait2 { static final Object key = new Object(); static boolean isPrettyGril = false; // 女人 /** * */ public static void main(String[] args) throws InterruptedException { new Thread(() -> { synchronized (key) { log.debug("有没有女人[{}]", isPrettyGril); if (!isPrettyGril) { log.debug("没有女人!等女人"); try { // 注意wait的对象是锁对象 key.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("老板喊我了 有没有女人?[{}]", isPrettyGril); if (isPrettyGril) { log.debug("---男女搭配干活不累;写完了代码"); }else{ log.debug("下班回家"); } } }, "jack").start(); for (int i = 0; i < 10; i++) { new Thread(() -> { synchronized (key) { log.debug("我们10个屌丝工作了"); } }, "其它人").start(); } Thread.sleep(1000); new Thread(() -> { synchronized (key) { isPrettyGril = true; log.debug("桥本有菜来了"); key.notify(); } }, "boss").start(); } }
问题:单个有条件的线程是没有问题的,设想如果有多个线程都需要满足条件
-
@Slf4j(topic = "enjoy") public class TestWait3 { static final Object key = new Object(); static boolean isPrettyGril = false; // 女人 static boolean isMoney = false;//工资 public static void main(String[] args) throws InterruptedException { new Thread(() -> { synchronized (key) { log.debug("有没有女人[{}]", isPrettyGril); if (!isPrettyGril) { log.debug("没有女人!等女人"); try { key.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("老板喊我了 有没有女人?[{}]", isPrettyGril); if (isPrettyGril) { log.debug("男女搭配干活不累;写完了代码"); }else { log.debug("不干了---下班回家--------"); } } }, "jack").start(); new Thread(() -> { synchronized (key) { log.debug("有没有工资[{}]", isMoney); if (!isMoney) { log.debug("没有工资!等发工资"); try { key.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("老板喊我了 有没有发工资?[{}]", isMoney); if (isMoney) { log.debug("-----卧槽好多钱;写完了代码"); }else { log.debug("约会去了"); } } }, "rose").start(); Thread.sleep(1000); new Thread(() -> { synchronized (key) { isMoney = true; log.debug("发工资了---"); // key.wait()的方法有两个 // 此时notify()会随机叫醒一个 key.notify(); } }, "boss").start(); } }
问题:虚假唤醒,怎么解决?
-
package com.enjoy.wait; import lombok.extern.slf4j.Slf4j; @Slf4j(topic = "enjoy") public class TestWait4 { static final Object key = new Object(); static boolean isPrettyGril = false; // 女人 static boolean isMoney = false;//工资 public static void main(String[] args) throws InterruptedException { new Thread(() -> { synchronized (key) { log.debug("有没有女人[{}]", isPrettyGril); while (!isPrettyGril) { log.debug("没有女人!等女人"); try { key.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("老板喊我了 有没有女人?[{}]", isPrettyGril); if (isPrettyGril) { log.debug("男女搭配干活不累;写完了代码"); }else { log.debug("不干了---下班回家--------"); } } }, "jack").start(); new Thread(() -> { synchronized (key) { log.debug("有没有工资[{}]", isMoney); if (!isMoney) { log.debug("没有工资!等发工资"); try { key.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("老板喊我了 有没有发工资?[{}]", isMoney); if (isMoney) { log.debug("-----卧槽好多钱;写完了代码"); } } }, "rose").start(); for (int i = 0; i < 10; i++) { new Thread(() -> { synchronized (key) { log.debug("我们10个屌丝工作了"); } }, "其它人").start(); } Thread.sleep(1000); new Thread(() -> { synchronized (key) { isMoney = true; log.debug("发工资了---"); // 同时注意key.wait()要用while包着 key.notifyAll(); } }, "boss").start(); } }
最终版
-
package com.enjoy.wait; import lombok.extern.slf4j.Slf4j; @Slf4j(topic = "enjoy") public class TestWait5 { static final Object key = new Object(); static boolean isPrettyGril = false; // 女人 static boolean isMoney = false;//工资 public static void main(String[] args) throws InterruptedException { new Thread(() -> { synchronized (key) { log.debug("有没有女人[{}]", isPrettyGril); while (!isPrettyGril) { log.debug("没有女人!等女人"); try { pc.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("老板喊我了 有没有女人?[{}]", isPrettyGril); if (isPrettyGril) { log.debug("男女搭配干活不累;写完了代码"); }else { log.debug("不干了---下班回家--------"); } } }, "jack").start(); new Thread(() -> { synchronized (key) { log.debug("有没有工资[{}]", isMoney); while (!isMoney) { log.debug("没有工资!等发工资"); try { pc.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("老板喊我了 有没有发工资?[{}]", isMoney); if (isMoney) { log.debug("-----卧槽好多钱;写完了代码"); } } }, "rose").start(); for (int i = 0; i < 10; i++) { new Thread(() -> { synchronized (key) { log.debug("我们10个屌丝工作了"); } }, "其它人").start(); } // 主线程睡眠1秒钟,保证上面的线程先拿到锁 Thread.sleep(1000); new Thread(() -> { synchronized (key) { isMoney = true; log.debug("发工资了---"); pc.notifyAll(); } }, "boss").start(); } }
wait可以带参数,表示阻塞时间
-
package com.shadow.test; import lombok.extern.slf4j.Slf4j; /** * 1、什么是对象头? * 什么是对象?内存级别而言来研究什么是对象 */ @Slf4j(topic = "enjoy") public class Test6 { static boolean isPrettyGril = false; static boolean isMoney = false; static Object key = new Object(); /** * 多线程情况下 假设某个线程拿到锁了,但是他需要满足某个条件 * 才能执行 如果用sleep 会导致在没有满足条件的情况下;我一直阻塞 * 一直持有锁,别的线程也拿不到锁 * 得有办公室的key * boss 需要叫jack 去加班编程;还有其他十个人是自愿加班 * jack不愿加班---你给我女人 * @param args * @throws InterruptedException */ public static void main(String[] args) throws InterruptedException { log.debug("mian--------"); new Thread(() -> { synchronized (key) { try { /** * wait可以带参数 * 第二个纳秒参数 是直接把毫秒+1 */ key.wait(5000,50); System.out.println("5秒钟之后 mian--end------"); } catch (InterruptedException e) { e.printStackTrace(); } } }, "boss").start(); Thread.sleep(100); } }
唤醒wait的方法
- notify/notifyAll
- Object.wait(5000),5秒后自己醒来
- 还有一个纳秒级别的,Object.wait(5000,50)
- 后面的纳秒,会直接在前面的基础上加1毫秒
- 打断interrupted
- wait方法通常和synchronized配合使用
- 一旦调用wait方法就会升级成重量锁(就算是偏向锁也是),就会有一个ObjectMonitor,线程才能和它关联,而这个对象里面才有waitset,才能存阻塞的线程