引言

关于源码分析如果不是功底特别深厚的小伙伴可能需要用心的去细心咀嚼,千万不要抱着看一边就能懂的心态学习,不然最终也没有任何作用。如果只是想要研究Synchronized关键字原理那么请观看我的上一篇文章:彻底理解Java并发编程之Synchronized关键字实现原理剖析。

五、Hotspot源码深度解读Synchronized关键字原理

从 monitorenter和 monitorexit这两个指令来开始阅读源码,JVM将字节码加载到内存以后,会对这两个指令进行解释执行, monitorenter, monitorexit的指令解析是通过 InterpreterRuntime.cpp中的两个方法实现:

/** JavaThread 当前获取锁的线程  BasicObjectLock 基础对象锁 **/
InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem)
InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem)

5.1、Hotspot中Synchronized关键字相关的源码位置

不过对于C/C++以及Hotspot源码目录不熟悉的小伙伴可以根据我提供目录找到对应的实现:

  • Monitor:openjdk\hotspot\src\share\vm\runtime\objectMonitor.hpp
  • MarkWord:openjdk\hotspot\src\share\vm\oops\markOop.hpp
  • monitorenter|exit指令:openjdk\hotspot\src\share\vm\interpreter\interpreterRuntime.cpp
  • 偏向锁:openjdk\hotspot\src\share\vm\runtime\biasedLocking.cpp

我们基于monitorenter为入口,沿着无锁态->偏向锁->轻量级锁->重量级锁的路径来分析synchronized的实现过程:

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERTthread->last_frame().interpreter_frame_verify_monitor(elem);
#endif...if (UseBiasedLocking) {// Retry fast entry if bias is revoked to avoid unnecessary inflationObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);} else {ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);}...
#ifdef ASSERTthread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END

通过观察大家会发现,如果我们启动了偏向锁,会执行 ObjectSynchronizer::fast_enter的逻辑,而如果我们没有开启偏向锁,则执行 ObjectSynchronizer::slow_enter逻辑,绕过偏向锁,直接进入轻量级锁。

5.2、开启偏向锁状态执行逻辑

ObjectSynchronizer::fast_enter的实现在 synchronizer.cpp文件中,具体实现如下:

void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {//判断是否开启了偏向锁if (UseBiasedLocking) { //如果不处于全局安全点if (!SafepointSynchronize::is_at_safepoint()) {//通过`revoke_and_rebias`这个函数尝试获取偏向锁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) ;
}

fast_enter实现简单流程:

  • 再次检查偏向锁是否已开启
  • 当处于不安全点时,通过 revoke_and_rebias尝试获取偏向锁,如果成功则直接返回,如果失败则进入轻量级锁获取过程
  • revoke_and_rebias这个偏向锁的获取逻辑在 biasedLocking.cpp中
  • 如果偏向锁未开启,则进入 slow_enter获取轻量级锁的流程

5.3、偏向锁获取逻辑

BiasedLocking::revoke_and_rebias 是用来获取当前偏向锁的状态(可能是偏向锁撤销后重新偏向)。这个方法的逻辑在 biasedLocking.cpp中,偏向锁获取逻辑具体实现如下:

BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) {assert(!SafepointSynchronize::is_at_safepoint(), "must not be called while at safepoint");markOop mark = obj->mark(); //获取锁对象的对象头//判断mark是否为可偏向状态,即mark的偏向锁标志位为1,锁标志位为 01,线程id为nullif (mark->is_biased_anonymously() && !attempt_rebias) {//这个分支是进行对象的hashCode计算时会进入,在一个非全局安全点进行偏向锁撤销markOop biased_value       = mark;//创建一个非偏向的markwordmarkOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());//Atomic:cmpxchg_ptr是CAS操作,通过cas重新设置偏向锁状态markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);if (res_mark == biased_value) {//如果CAS成功,返回偏向锁撤销状态return BIAS_REVOKED;}} else if (mark->has_bias_pattern()) {//如果锁对象为可偏向状态(biased_lock:1, lock:01,不管线程id是否为空),尝试重新偏向Klass* k = obj->klass(); markOop prototype_header = k->prototype_header();//如果已经有线程对锁对象进行了全局锁定,则取消偏向锁操作if (!prototype_header->has_bias_pattern()) {markOop biased_value       = mark;//CAS 更新对象头markword为非偏向锁markOop res_mark = (markOop) Atomic::cmpxchg_ptr(prototype_header, obj->mark_addr(), mark);assert(!(*(obj->mark_addr()))->has_bias_pattern(), "even if we raced, should still be revoked");return BIAS_REVOKED; //返回偏向锁撤销状态} else if (prototype_header->bias_epoch() != mark->bias_epoch()) {//如果偏向锁过期,则进入当前分支if (attempt_rebias) {//如果允许尝试获取偏向锁assert(THREAD->is_Java_thread(), "");markOop biased_value       = mark;markOop rebiased_prototype = markOopDesc::encode((JavaThread*) THREAD, mark->age(), prototype_header->bias_epoch());//通过CAS 操作, 将本线程的 ThreadID 、时间错、分代年龄尝试写入对象头中markOop res_mark = (markOop) Atomic::cmpxchg_ptr(rebiased_prototype, obj->mark_addr(), mark);if (res_mark == biased_value) { //CAS成功,则返回撤销和重新偏向状态return BIAS_REVOKED_AND_REBIASED;}} else {//不尝试获取偏向锁,则取消偏向锁//通过CAS操作更新分代年龄markOop biased_value       = mark;markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);if (res_mark == biased_value) { //如果CAS操作成功,返回偏向锁撤销状态return BIAS_REVOKED;}}}}...//省略
}

5.4、偏向锁撤销逻辑

当到达一个全局安全点时,这时会根据偏向锁的状态来判断是否需要撤销偏向锁,调用 revoke_at_safepoint方法,这个方法也是在 biasedLocking.cpp中定义的,具体实现如下:

void BiasedLocking::revoke_at_safepoint(Handle h_obj) {assert(SafepointSynchronize::is_at_safepoint(), "must only be called while at safepoint");oop obj = h_obj();//更新撤销偏向锁计数,并返回偏向锁撤销次数和偏向次数HeuristicsResult heuristics = update_heuristics(obj, false);if (heuristics == HR_SINGLE_REVOKE) {//可偏向且未达到批量处理的阈值(下面会单独解释)revoke_bias(obj, false, false, NULL); //撤销偏向锁} else if ((heuristics == HR_BULK_REBIAS) || (heuristics == HR_BULK_REVOKE)) {//如果是多次撤销或者多次偏向//批量撤销bulk_revoke_or_rebias_at_safepoint(obj, (heuristics == HR_BULK_REBIAS), false, NULL);}clean_up_cached_monitor_info();
}

偏向锁的释放,需要等待全局安全点(在这个时间点上没有正在执行的字节码),首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否还活着,如果线程不处于活动状态,则将对象头设置成无锁状态。如果线程仍然活着,则会升级为轻量级锁,遍历偏向对象的所记录。栈帧中的锁记录和对象头的Mark Word要么重新偏向其他线程,要么恢复到无锁,或者标记对象不适合作为偏向锁。最后唤醒暂停的线程。

JVM内部为每个类维护了一个偏向锁revoke计数器,对偏向锁撤销进行计数,当这个值达到指定阈值时,JVM会认为这个类的偏向锁有问题,需要重新偏向(rebias),对所有属于这个类的对象进行重偏向的操作成为 批量重偏向(bulk rebias)。在做bulk rebias时,会对这个类的epoch的值做递增,这个epoch会存储在对象头中的epoch字段。在判断这个对象是否获得偏向锁的条件是:markword的 biased_lock:1、lock:01、threadid和当前线程id相等、epoch字段和所属类的epoch值相同,如果epoch的值不一样,要么就是撤销偏向锁、要么就是rebias; 如果这个类的revoke计数器的值继续增加到一个阈值,那么jvm会认为这个类不适合偏向锁,就需要进行bulk revoke操作。

5.5、轻量级锁获取逻辑

轻量级锁获取是调用 ::slow_enter方法,该方法同样位于 synchronizer.cpp文件中,具体实现如下:

void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {markOop mark = obj->mark();assert(!mark->has_bias_pattern(), "should not see bias pattern here");if (mark->is_neutral()) { //如果当前是无锁状态, markword的biase_lock:0,lock:01//直接把mark保存到BasicLock对象的_displaced_header字段lock->set_displaced_header(mark);//通过CAS将mark word更新为指向BasicLock对象的指针,更新成功表示获得了轻量级锁if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {TEVENT (slow_enter: release stacklock) ;return ;}// Fall through to inflate() ... }//如果markword处于加锁状态、且markword中的ptr指针指向当前线程的栈帧,表示为重入操作,不需要争抢锁 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");lock->set_displaced_header(NULL);return;}#if 0// The following optimization isn't particularly useful.if (mark->has_monitor() && mark->monitor()->is_entered(THREAD)) {lock->set_displaced_header (NULL) ;return ;}
#endif//代码执行到这里,说明有多个线程竞争轻量级锁,轻量级锁通过`inflate`进行膨胀升级为重量级锁lock->set_displaced_header(markOopDesc::unused_mark());ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}

简单整理轻量级锁的获取逻辑:

  • mark->is_neutral()方法, is_neutral这个方法是在 markOop.hpp中定义,如果 biased_lock:0且lock:01表示无锁状态
  • 如果mark处于无锁状态,则进入下一步骤,否则执行最后一个步骤
  • 把mark保存到BasicLock对象的displacedheader字段
  • 通过CAS尝试将markword更新为指向BasicLock对象的指针,如果更新成功,表示竞争到锁,则执行同步代码,否则执行下一步骤
  • 如果当前mark处于加锁状态,且mark中的ptr指针指向当前线程的栈帧,则执行同步代码,否则说明有多个线程竞争轻量级锁,轻量级锁需要膨胀升级为重量级锁

5.6、轻量级锁释放逻辑

轻量级锁的释放是通过 monitorexit调用,具体实现如下:

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERTthread->last_frame().interpreter_frame_verify_monitor(elem);
#endifHandle h_obj(thread, elem->obj());assert(Universe::heap()->is_in_reserved_or_null(h_obj()),"must be NULL or an object");if (elem == NULL || h_obj()->is_unlocked()) {THROW(vmSymbols::java_lang_IllegalMonitorStateException());}ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread);// Free entry. This must be done here, since a pending exception might be installed on// exit. If it is not cleared, the exception handling code will try to unlock the monitor again.elem->set_obj(NULL);
#ifdef ASSERTthread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END

这段代码中主要是通过 ObjectSynchronizer::slow_exit来执行,具体实现如下:

void ObjectSynchronizer::slow_exit(oop object, BasicLock* lock, TRAPS) {fast_exit (object, lock, THREAD) ;
}

ObjectSynchronizer::fast_exit的代码如下:

void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {assert(!object->mark()->has_bias_pattern(), "should not see bias pattern here");// if displaced header is null, the previous enter is recursive enter, no-opmarkOop dhw = lock->displaced_header(); //获取锁对象中的对象头markOop mark ;if (dhw == NULL) { // Recursive stack-lock.// Diagnostics -- Could be: stack-locked, inflating, inflated.mark = object->mark() ;assert (!mark->is_neutral(), "invariant") ;if (mark->has_locker() && mark != markOopDesc::INFLATING()) {assert(THREAD->is_lock_owned((address)mark->locker()), "invariant") ;}if (mark->has_monitor()) {ObjectMonitor * m = mark->monitor() ;assert(((oop)(m->object()))->mark() == mark, "invariant") ;assert(m->is_entered(THREAD), "invariant") ;}return ;}mark = object->mark() ; //获取线程栈帧中锁记录(LockRecord)中的markword// If the object is stack-locked by the current thread, try to// swing the displaced header from the box back to the mark.if (mark == (markOop) lock) {assert (dhw->is_neutral(), "invariant") ;//通过CAS尝试将Displaced Mark Word替换回对象头,如果成功,表示锁释放成功。if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {TEVENT (fast_exit: release stacklock) ;return;}}//锁膨胀,调用重量级锁的释放锁方法ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;
}

轻量级锁的释放也比较简单,就是将当前线程栈帧中锁记录空间中的Mark Word替换到锁对象的对象头中,如果成功表示锁释放成功。否则,锁膨胀成重量级锁,实现重量级锁的释放锁逻辑。

5.7、锁膨胀过程分析

重量级锁是通过对象内部的监视器(monitor)来实现,而monitor的本质是依赖操作系统底层的MutexLock实现的。我们先来看锁的膨胀过程,从前面的分析中已经知道了所膨胀的过程是通过 ObjectSynchronizer::inflate方法实现的,代码如下:

ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {// Inflate mutates the heap ...// Relaxing assertion for bug 6320749.assert (Universe::verify_in_progress() ||!SafepointSynchronize::is_at_safepoint(), "invariant") ;for (;;) { //通过无意义的循环实现自旋操作const markOop mark = object->mark() ;assert (!mark->has_bias_pattern(), "invariant") ;if (mark->has_monitor()) {//has_monitor是markOop.hpp中的方法,如果为true表示当前锁已经是重量级锁了ObjectMonitor * inf = mark->monitor() ;//获得重量级锁的对象监视器直接返回assert (inf->header()->is_neutral(), "invariant");assert (inf->object() == object, "invariant") ;assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is invalid");return inf ;}if (mark == markOopDesc::INFLATING()) {//膨胀等待,表示存在线程正在膨胀,通过continue进行下一轮的膨胀TEVENT (Inflate: spin while INFLATING) ;ReadStableMark(object) ;continue ;}if (mark->has_locker()) {//表示当前锁为轻量级锁,以下是轻量级锁的膨胀逻辑ObjectMonitor * m = omAlloc (Self) ;//获取一个可用的ObjectMonitor// Optimistically prepare the objectmonitor - anticipate successful CAS// We do this before the CAS in order to minimize the length of time// in which INFLATING appears in the mark.m->Recycle();m->_Responsible  = NULL ;m->OwnerIsThread = 0 ;m->_recursions   = 0 ;m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ;   // Consider: maintain by type/class/**将object->mark_addr()和mark比较,如果这两个值相等,则将object->mark_addr()改成markOopDesc::INFLATING(),相等返回是mark,不相等返回的是object->mark_addr()**/markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;if (cmp != mark) {//CAS失败omRelease (Self, m, true) ;//释放监视器continue ;       // 重试}markOop dmw = mark->displaced_mark_helper() ;assert (dmw->is_neutral(), "invariant") ;//CAS成功以后,设置ObjectMonitor相关属性m->set_header(dmw) ;m->set_owner(mark->locker());m->set_object(object);// TODO-FIXME: assert BasicLock->dhw != 0.guarantee (object->mark() == markOopDesc::INFLATING(), "invariant") ;object->release_set_mark(markOopDesc::encode(m));if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;TEVENT(Inflate: overwrite stacklock) ;if (TraceMonitorInflation) {if (object->is_instance()) {ResourceMark rm;tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",(void *) object, (intptr_t) object->mark(),object->klass()->external_name());}}return m ; //返回ObjectMonitor}//如果是无锁状态assert (mark->is_neutral(), "invariant");ObjectMonitor * m = omAlloc (Self) ; ////获取一个可用的ObjectMonitor//设置ObjectMonitor相关属性m->Recycle();m->set_header(mark);m->set_owner(NULL);m->set_object(object);m->OwnerIsThread = 1 ;m->_recursions   = 0 ;m->_Responsible  = NULL ;m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ;       // consider: keep metastats by type/class/**将object->mark_addr()和mark比较,如果这两个值相等,则将object->mark_addr()改成markOopDesc::encode(m),相等返回是mark,不相等返回的是object->mark_addr()**/if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) {//CAS失败,说明出现了锁竞争,则释放监视器重行竞争锁m->set_object (NULL) ;m->set_owner  (NULL) ;m->OwnerIsThread = 0 ;m->Recycle() ;omRelease (Self, m, true) ;m = NULL ;continue ;// interference - the markword changed - just retry.// The state-transitions are one-way, so there's no chance of// live-lock -- "Inflated" is an absorbing state.}if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;TEVENT(Inflate: overwrite neutral) ;if (TraceMonitorInflation) {if (object->is_instance()) {ResourceMark rm;tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",(void *) object, (intptr_t) object->mark(),object->klass()->external_name());}}return m ; //返回ObjectMonitor对象}
}

锁膨胀的过程稍微有点复杂,整个锁膨胀的过程是通过自旋来完成的,具体的实现逻辑简答总结以下几点:

  • mark->has_monitor() 判断如果当前锁对象为重量级锁,也就是lock:10,则执行第二步骤,否则执行第三步骤。
  • 通过 mark->monitor获得重量级锁的对象监视器ObjectMonitor并返回,锁膨胀过程结束。
  • 如果当前锁处于 INFLATING,说明有其他线程在执行锁膨胀,那么当前线程通过自旋等待其他线程锁膨胀完成。
  • 如果当前是轻量级锁状态 mark->has_locker(),则进行锁膨胀。首先,通过omAlloc方法获得一个可用的ObjectMonitor,并设置初始数据;然后通过CAS将对象头设置为`markOopDesc:INFLATING,表示当前锁正在膨胀,如果CAS失败,继续自旋。
  • 如果是无锁状态,逻辑类似第四步骤。

锁膨胀的过程实际上是获得一个ObjectMonitor对象监视器,而真正抢占锁的逻辑,在 ObjectMonitor::enter方法里面。

5.8、重量级锁的竞争逻辑

重量级锁的竞争,在 ObjectMonitor::enter方法中,代码文件在 objectMonitor.cpp重量级锁的代码就不一一分析了,简单说一下下面这段代码主要做的几件事:

  • 通过CAS将monitor的 _owner字段设置为当前线程,如果设置成功,则直接返回。
  • 如果之前的 _owner指向的是当前的线程,说明是重入,执行 _recursions++增加重入次数。
  • 如果当前线程获取监视器锁成功,将 _recursions设置为1, _owner设置为当前线程。
  • 如果获取锁失败,则等待锁释放。
void ATTR 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 ;cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;if (cur == NULL) {//CAS成功// Either ASSERT _recursions == 0 or explicitly set _recursions = 0.assert (_recursions == 0   , "invariant") ;assert (_owner      == Self, "invariant") ;// CONSIDER: set or assert OwnerIsThread == 1return ;}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 ;OwnerIsThread = 1 ;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 (Knob_SpinEarly && 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_ptr(&_count);EventJavaMonitorEnter event;{ // Change java thread status to indicate blocked on monitor enter.JavaThreadBlockedOnMonitorEnterState jtbmes(jt, this);DTRACE_MONITOR_PROBE(contended__enter, this, object(), jt);if (JvmtiExport::should_post_monitor_contended_enter()) {JvmtiExport::post_monitor_contended_enter(jt, this);}OSThreadContendState osts(Self->osthread());ThreadBlockInVM tbivm(jt);Self->set_current_pending_monitor(this);// 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 (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);}
...//此处省略无数行代码

如果获取锁失败,则需要通过自旋的方式等待锁释放,自旋执行的方法是 ObjectMonitor::EnterI,部分原理以及代码如下:

  • 将当前线程封装成ObjectWaiter对象node,状态设置成TS_CXQ。
  • 通过自旋操作将node节点push到_cxq队列。
  • node节点添加到_cxq队列之后,继续通过自旋尝试获取锁,如果在指定的阈值范围内没有获得锁,则通过park将当前线程挂起,等待被唤醒。
void ATTR ObjectMonitor::EnterI (TRAPS) {Thread * Self = THREAD ;...//省略很多代码ObjectWaiter node(Self) ;Self->_ParkEvent->reset() ;node._prev   = (ObjectWaiter *) 0xBAD ;node.TState  = ObjectWaiter::TS_CXQ ;// Push "Self" onto the front of the _cxq.// Once on cxq/EntryList, Self stays on-queue until it acquires the lock.// Note that spinning tends to reduce the rate at which threads// enqueue and dequeue on EntryList|cxq.ObjectWaiter * nxt ;for (;;) { //自旋,讲node添加到_cxq队列node._next = nxt = _cxq ;if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;// Interference - the CAS failed because _cxq changed.  Just retry.// As an optional optimization we retry the lock.if (TryLock (Self) > 0) {assert (_succ != Self         , "invariant") ;assert (_owner == Self        , "invariant") ;assert (_Responsible != Self  , "invariant") ;return ;}}...//省略很多代码//node节点添加到_cxq队列之后,继续通过自旋尝试获取锁,如果在指定的阈值范围内没有获得锁,则通过park将当前线程挂起,等待被唤醒for (;;) {if (TryLock (Self) > 0) break ;assert (_owner != Self, "invariant") ;if ((SyncFlags & 2) && _Responsible == NULL) {Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;}// park self //通过park挂起当前线程if (_Responsible == Self || (SyncFlags & 1)) {TEVENT (Inflated enter - park TIMED) ;Self->_ParkEvent->park ((jlong) RecheckInterval) ;// Increase the RecheckInterval, but clamp the value.RecheckInterval *= 8 ;if (RecheckInterval > 1000) RecheckInterval = 1000 ;} else {TEVENT (Inflated enter - park UNTIMED) ;Self->_ParkEvent->park() ;//当前线程挂起}if (TryLock(Self) > 0) break ; //当线程被唤醒时,会从这里继续执行TEVENT (Inflated enter - Futile wakeup) ;if (ObjectMonitor::_sync_FutileWakeups != NULL) {ObjectMonitor::_sync_FutileWakeups->inc() ;}++ nWakeups ;if ((Knob_SpinAfterFutile & 1) && TrySpin (Self) > 0) break ;if ((Knob_ResetEvent & 1) && Self->_ParkEvent->fired()) {Self->_ParkEvent->reset() ;OrderAccess::fence() ;}if (_succ == Self) _succ = NULL ;// Invariant: after clearing _succ a thread *must* retry _owner before parking.OrderAccess::fence() ;}...//省略很多代码
}

TryLock(self)的代码是在 ObjectMonitor::TryLock定义的,代码的实现如下:

代码的实现原理很简单,通过自旋,CAS设置monitor的_owner字段为当前线程,如果成功,表示获取到了锁,如果失败,则继续被挂起。

int ObjectMonitor::TryLock (Thread * Self) {for (;;) {void * own = _owner ;if (own != NULL) return 0 ;if (Atomic::cmpxchg_ptr (Self, &_owner, NULL) == NULL) {// Either guarantee _recursions == 0 or set _recursions = 0.assert (_recursions == 0, "invariant") ;assert (_owner == Self, "invariant") ;// CONSIDER: set or assert that OwnerIsThread == 1return 1 ;}// The lock had been free momentarily, but we lost the race to the lock.// Interference -- the CAS failed.// We can either return -1 or retry.// Retry doesn't make as much sense because the lock was just acquired.if (true) return -1 ;}
}

5.9、重量级锁的释放

重量级锁的释放是通过 ObjectMonitor::exit来实现的,释放以后会通知被阻塞的线程去竞争锁:

  • 判断当前锁对象中的owner没有指向当前线程,如果owner指向的BasicLock在当前线程栈上,那么将_owner指向当前线程。
  • 如果当前锁对象中的_owner指向当前线程,则判断当前线程重入锁的次数,如果不为0,继续执行ObjectMonitor::exit(),直到重入锁次数为0为止。
  • 释放当前锁,并根据QMode的模式判断,是否将_cxq中挂起的线程唤醒。还是其他操作。
void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {Thread * Self = THREAD ;if (THREAD != _owner) {//如果当前锁对象中的_owner没有指向当前线程//如果_owner指向的BasicLock在当前线程栈上,那么将_owner指向当前线程if (THREAD->is_lock_owned((address) _owner)) {// Transmute _owner from a BasicLock pointer to a Thread address.// We don't need to hold _mutex for this transition.// Non-null to Non-null is safe as long as all readers can// tolerate either flavor.assert (_recursions == 0, "invariant") ;_owner = THREAD ;_recursions = 0 ;OwnerIsThread = 1 ;} else {// NOTE: we need to handle unbalanced monitor enter/exit// in native code by throwing an exception.// TODO: Throw an IllegalMonitorStateException ?TEVENT (Exit - Throw IMSX) ;assert(false, "Non-balanced monitor enter/exit!");if (false) {THROW(vmSymbols::java_lang_IllegalMonitorStateException());}return;}}//如果当前,线程重入锁的次数,不为0,那么就重新走ObjectMonitor::exit,直到重入锁次数为0为止if (_recursions != 0) {_recursions--;        // this is simple recursive enterTEVENT (Inflated exit - recursive) ;return ;}...//此处省略很多代码for (;;) {if (Knob_ExitPolicy == 0) {OrderAccess::release_store(&_owner, (void*)NULL);   //释放锁OrderAccess::storeload();                        // See if we need to wake a successorif ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {TEVENT(Inflated exit - simple egress);return;}TEVENT(Inflated exit - complex egress);//省略部分代码...}//省略部分代码...ObjectWaiter * w = NULL;int QMode = Knob_QMode;//根据QMode的模式判断,//如果QMode == 2则直接从_cxq挂起的线程中唤醒    if (QMode == 2 && _cxq != NULL) {w = _cxq;ExitEpilog(Self, w);return;}//省略部分代码... 省略的代码为根据QMode的不同,不同的唤醒机制}
}

根据不同的策略(由QMode指定),从cxq或EntryList中获取头节点,通过ObjectMonitor::ExitEpilog方法唤醒该节点封装的线程,唤醒操作最终由unpark完成。

void ObjectMonitor::ExitEpilog (Thread * Self, ObjectWaiter * Wakee) {
{assert (_owner == Self, "invariant") ;// Exit protocol:// 1. ST _succ = wakee// 2. membar #loadstore|#storestore;// 2. ST _owner = NULL// 3. unpark(wakee)_succ = Knob_SuccEnabled ? Wakee->_thread : NULL ;ParkEvent * Trigger = Wakee->_event ;// Hygiene -- once we've set _owner = NULL we can't safely dereference Wakee again.// The thread associated with Wakee may have grabbed the lock and "Wakee" may be// out-of-scope (non-extant).Wakee  = NULL ;// Drop the lockOrderAccess::release_store_ptr (&_owner, NULL) ;OrderAccess::fence() ;                               // ST _owner vs LD in unpark()if (SafepointSynchronize::do_call_back()) {TEVENT (unpark before SAFEPOINT) ;}DTRACE_MONITOR_PROBE(contended__exit, this, object(), Self);Trigger->unpark() ; //unpark唤醒线程// Maintain stats and report events to JVMTIif (ObjectMonitor::_sync_Parks != NULL) {ObjectMonitor::_sync_Parks->inc() ;}
}

再次提醒:关于源码分析如果不是功底特别深厚的小伙伴可能需要用心的去细心咀嚼,千万不要抱着看一边就能懂的心态学习,不然最终也没有任何作用。

六、参考资料

  • 《深入理解JVM虚拟机》
  • 《Java并发编程之美》
  • 《Java高并发程序设计》
  • 《亿级流量网站架构核心技术》
  • 《Java并发编程实战》
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 商品信息

    // 全局定义 var app = getApp(); var baseUrl = app.globalData.host;var arr = []; Page({/*** 页面的初始数据*/data: {interval2: 4000,duration: 2000,activeColor: "#fff",page_select: "page",jb: true,jc: true,showToast: ,t1m: ,t1b: ,tl: ,swip…...

    2024/4/24 14:51:33
  2. 在微信小程序上写收藏

    在详情页的页面上先放上他的默认收藏图标和收藏之后图标2.在详情页的js里面的data里面给添加收藏和取消收藏绑定一个true和false,然后在页面去利用wx:if去给他绑定3.写上他的添加收藏方法和取消收藏方法 4.由于添加了之后虽然说他的添加收藏之后的状态,但是返回之后,再回到收…...

    2024/5/9 5:50:07
  3. Java-this指针、Static关键字、代码块、Package、Import、封装

    this指针 /* * this指针 * this:表示当前对象的指针 * * 指向当前对象,表示当前对象的引用* 用处:* 1、构造方法,当构造方法中的参数名称跟类的成员变量名称一样的时候,可以使用this代表当前对象* 注意:有了this之后,可以将构造方法的参数跟成员变量…...

    2024/5/8 16:58:27
  4. 前++和后++

    2020年6月22日 又被“++”运算给恶心到了,在这里来个了断 情况一: 在独立运算过程中 例如: {...i++;//或者i--++i;前置++和后置++没有区别都可以使用 }情况二: 在牵扯到赋值情况 例如: {(b=i++;)等价于{b=i;i=i+1;}(b=++i;)等价于{i=i+1;b=i;} }...

    2024/4/24 14:51:29
  5. SpringBoot集成WebService

    SpringBoot集成WebService导读dependencyservice访问 导读 找了一天尝试了很多种方法,最终才搞定,分享一下 谢谢老板 springboot版本号为2.3.1.RELEASE。 dependency<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-…...

    2024/5/2 19:43:02
  6. SLF4J的简单使用和Logback配置

    SLF4J介绍 SLF4J官网:http://www.slf4j.org/The Simple Logging Facade for Java (SLF4J) serves as a simple facade or abstraction for various logging frameworks (e.g. java.util.logging, logback, log4j) allowing the end user to plug in the desired logging frame…...

    2024/5/2 10:51:41
  7. 概率论组队学习

    随机事件与随机变量 随机现象: 现实生活中,一个动作或一件事情,在一定条件下,所得的结果不能预先完全确定,而只能确定是多种可能结果中的一种。 样本空间:一个试验所有可能的集合。 样本点:试验的每一种可能的结果。 随机事件:样本空间满足一定条件的子集。 概率 定义:…...

    2024/4/24 14:51:27
  8. JeeSite快速开始与环境搭建

    官方文档:传送门 一、准备环境Java SDK 8 or 11 Servlet 3.0、3.1+ Apache Maven 3.x二、拉取项目 git clone https://gitee.com/thinkgem/jeesite4.git三、导入项目四、初始化数据库 1、打开 my.ini 给 [mysqld] 增加如下配置: sql_mode="ONLY_FULL_GROUP_BY,STRICT_TR…...

    2024/4/24 14:51:25
  9. 多系统精密星历下载与分析

    概述 精密星历是由若干卫星跟踪站的观测数据,经事后处理算得的供卫星精密定位等使用的卫星轨道信息。IGS精密星历采用sp3格式,其存储方式为ASCII文本文件,内容包括表头信息以及文件体,文件体中每隔15 min给出1个卫星的位置,有时还给出卫星的速度。它的特点就是提供卫星精确…...

    2024/4/24 14:51:27
  10. FPGA学习:快速编写约束文件

    如何快速编写约束文件 对于FPGA的初学者,在编写约束文件的管脚约束时,编写习惯为对照原理图来寻找引脚编号,然后一一编写入.xdc文件(Vivado约束文件格式)中,或者在GUI(图形化操作界面)中使用edit timing constraints,这两种做法都费时费力。 如何快速编写管脚约束呢?…...

    2024/4/24 14:51:23
  11. movielens1m数据集处理

    @[TOC]电影推荐系统 一、对ml-1m数据进行清洗 1.对电影数据进行清洗 import pandas as pd import numpy as np# 读取电影数据 datas = pd.read_csv(datas/001-movie.csv) # 取出类别数据 Class = datas.genres # 定义一个用于保存整体类别数据的列表(有重复) all_class = [] …...

    2024/4/24 14:51:22
  12. Master of GCD(差分)

    题目链接 题意:给你一个含n个1的序列,让你进行m个操作,每个操作要向一段区间进行乘2或3,问你最终这段序列的最小公约数为多少。 思路:最终每个数肯定是有一定数量的2和3累乘得到,所以直接找每个位置2和3乘的最小次数就是整段序列的最小公约数(注:用cin,cout会t,别问为…...

    2024/5/2 13:58:28
  13. spring框架第四章之AOP(注解开发)

    spring框架第四章之AOP(注解开发) 注解介绍 和配置文件不一样,注解开发更加简洁,并且可以提高一定程度的开发速度。能够减少配置文件。注解 说明@Aspect 声明这是一个切面类@Before 配置前置通知,指定增强方法在切入点之前执行@AfterReturning 配置后置返回通知,指定增强…...

    2024/4/19 23:14:34
  14. 反射,动态代理一锅煮

    反射(第一次写博客,格式还望朋友们指点) 通过类的字节码文件,操作类的属性,方法,构造,不需要通过对象就能实现 class对象获取方式: 1.类名.class 2.实例.getClass 3.Class.forName()操作构造器: 获得任意修饰符的构造器对象 getDeclaredConstructor(Obj.class) 创建对象 ne…...

    2024/4/15 3:03:19
  15. 东北大学OJ-1355: 实验8-1:求矩阵右上角之和(二维数组、字符数组)

    东北大学OJ-1355: 实验8-1:求矩阵右上角之和(二维数组、字符数组)大家好,我叫亓官劼(q guān ji ),在CSDN中记录学习的点滴历程,时光荏苒,未来可期,加油~博客地址为:亓官劼的博客,B站昵称为:亓官劼,地址为亓官劼的B站本文原创为亓官劼,请大家支持原创,部分平台…...

    2024/4/16 14:32:55
  16. Android开发中控制UI界面的方式

    Android开发中控制UI界面的方式1.使用XML布局文件控制UI界面(推荐)2.在Java代码中控制UI界面public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);FrameLayout frameLa…...

    2024/5/2 14:34:26
  17. 2020-06-22:已知两个非负数的异或值为M,两数之和为N,求这两个数?

    参考答案如下: 1.遍历法 时间复杂度:O(N) 空间复杂度:O(logN) [0,N/2]依次遍历,符合条件的就是需要的结果。 2.位运算法 时间复杂度:O(logN) 空间复杂度:O(logN) 1100100 两数和N=100,已知 0010100 异或值M=20,已知 1010000 差N-M=80,如果差为负数或者差为奇数,直接返…...

    2024/4/24 14:51:21
  18. JavaScript基础----弹窗

    <!DOCTYPE html> <html> <head><meta charset="utf-8"><title></title> </head> <body><script type="text/javascript">var a="小猪佩奇";console.log(a);var b;console.log(b);var c=1…...

    2024/4/24 14:51:19
  19. EL表达式和JSTL学习复习(2)

    JSTL使用 1.什么是JSTL ​ JSTL是Jsp Standard Tag Library的缩写,jsp标准标签库,用来替代jsp中的代码脚本,是其更为简洁、规范。 2.JSTL使用步骤 ​ 在使用JSTL时要导入相关的jar包,taglibs-standard-impl-1.2.1.jar和taglibs-standard-spec-1.2.1.jar两个jar包,然后使用…...

    2024/4/24 14:51:18
  20. jsp页面中的为文本提供可输入选项的简单方法

    <input type="text" list="sexList"name="sex" id="sex" value="${customer.sex}"/></td><datalist id="sexlist"><option>男</option><option>女</option></datal…...

    2024/4/24 14:51:20

最新文章

  1. 跟我学做零售数据分析报表-商品滞销分析

    商品滞销的情况很常见&#xff0c;因此商品滞销分析也是基本属于零售数据分析标配内容之一。那么&#xff0c;商品滞销分析报表该怎么做&#xff1f;要做计算哪些指标&#xff0c;怎么分析滞销趋势&#xff1f;别急&#xff0c;奥威BI零售数据分析方案预设了一张BI商品滞销分析…...

    2024/5/9 7:29:21
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/5/7 10:36:02
  3. 2024.4.10作业

    #include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); } Widget::~Widget() { delete ui; } //显示时间 void Widget::timerEvent(QTimerEvent *e) { QT…...

    2024/5/8 15:55:38
  4. 利用Sentinel解决雪崩问题(一)

    1、解决雪崩问题的常见方式有四种: 超时处理:设定超时时间&#xff0c;请求超过一定时间没有响应就返回错误信息&#xff0c;不会无休止等待;舱壁模式:限定每个业务能使用的线程数&#xff0c;避免耗尽整个tomcat的资源&#xff0c;因此也叫线程隔离;熔断降级:由断路器统计业务…...

    2024/5/4 23:53:05
  5. 北航2023年考研机试题

    【问题描述】一共6个手机基站&#xff0c;具有记录手机连接基站的能力&#xff0c;6个手机基站分别为ABCDEF&#xff0c;他们具有自己的覆盖范围且任何两个基站的覆盖范围不想交&#xff0c;基站保存的手机登陆日志包括手机号&#xff08;11位&#xff0c;用字符串保存&#xf…...

    2024/5/5 8:38:20
  6. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/8 6:01:22
  7. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/7 9:45:25
  8. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/5/4 23:54:56
  9. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/9 4:20:59
  10. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/5/4 23:54:56
  11. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/5/4 23:55:05
  12. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/5/4 23:54:56
  13. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/5/7 11:36:39
  14. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/5/4 23:54:56
  15. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/6 1:40:42
  16. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/5/4 23:54:56
  17. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/5/8 20:48:49
  18. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/5/7 9:26:26
  19. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/5/4 23:54:56
  20. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/8 19:33:07
  21. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/5/5 8:13:33
  22. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/5/8 20:38:49
  23. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/5/4 23:54:58
  24. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/6 21:42:42
  25. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/5/4 23:54:56
  26. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  27. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  28. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  29. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  30. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  31. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  32. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  33. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  34. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  35. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  36. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  37. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  38. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  39. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  40. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  41. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  42. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  43. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  44. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  45. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57