掌握Android和Java线程原理下,2021年Android高级面试题总结
monitorenter主要根据虚拟机是否开启偏向锁来进行偏向锁加锁,如果没开启,则进行自旋锁或重量级锁加锁。先看偏向锁的加锁流程,它的实现在fast_enter函数。
偏向锁加锁流程
//文件–>\src\share\vm\runtime\synchronizer.cpp
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) ;
}
fast_enter的关键流程在revoke_and_rebias函数中实现,函数中的逻辑主要如下:
-
判断markwork是否为偏向锁状态,也就是偏向锁标志位是否为 1,如果为是偏向锁状态,进入下一步检测,如果不是,直接通过CAS进行偏向锁加锁,加锁成功后就可进入临界区执行临界区的字节码;
-
如果是偏向锁状态,则检测markwork中ThreadId,如果指向当前线程,则可以直接进入临界区;如果为空,则进入步骤3;如果指向其它线程,进入步骤4;
-
通过CAS设置markwork中ThreadId为当前线程ID,如果执行CAS成功,表示偏向锁加锁成功,进入临界区,否则进入步骤4;
-
如果执行CAS失败,表示当前存在多个线程竞争锁,撤销偏向锁,执行slow_enter流程。
BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) {
// We can revoke the biases of anonymously-biased objects
// efficiently enough that we should not cause these revocations to
// update the heuristics because doing so may cause unwanted bulk
// revocations (which are expensive) to occur.
markOop mark = obj->mark();
if (mark->is_biased_anonymously() && !attempt_rebias) {
//匿名偏向状态,即ThreadId为0以及偏向标志关闭,则需要撤销偏向锁。
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) {
//返回BIAS_REVOKED标志后,fast_enter函数中会接着走slow_enter逻辑
return BIAS_REVOKED;
}
} else if (mark->has_bias_pattern()) {
Klass* k = obj->klass();
markOop prototype_header = k->prototype_header();
if (!prototype_header->has_bias_pattern()) {
//如果关闭偏向锁模式,则需要撤销偏向锁
markOop biased_value = mark;
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;
//如果attempt_rebias开启,重新设置过期时间
markOop rebiased_prototype = markOopDesc::encode((JavaThread*) THREAD, mark->age(), prototype_header->bias_epoch());
markOop res_mark = (markOop) Atomic::cmpxchg_ptr(rebiased_prototype, obj->mark_addr(), mark);
if (res_mark == biased_value) {
return BIAS_REVOKED_AND_REBIASED;
}
} else {
//如果attempt_rebias关闭,则撤销偏向锁
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) {
return BIAS_REVOKED;
}
}
}
}
//更新撤销偏向锁计数,并返回偏向锁撤销次数和偏向次数
HeuristicsResult heuristics = update_heuristics(obj(), attempt_rebias);
if (heuristics == HR_NOT_BIASED) {
return NOT_BIASED;
} else if (heuristics == HR_SINGLE_REVOKE) {
//如果要撤销或者重偏向偏向锁的线程是当前线程,则直接撤销当前线程线程的偏向锁
Klass *k = obj->klass();
markOop prototype_header = k->prototype_header();
if (mark->biased_locker() == THREAD &&
prototype_header->bias_epoch() == mark->bias_epoch()) {
ResourceMark rm;
if (TraceBiasedLocking) {
tty->print_cr(“Revoking bias by walking my own stack:”);
}
//撤销偏向锁或者重偏向
BiasedLocking::Condition cond = revoke_bias(obj(), false, false, (JavaThread*) THREAD);
((JavaThread*) THREAD)->set_cached_monitor_info(NULL);
assert(cond == BIAS_REVOKED, “why not?”);
return cond;
} else {
//如果不是当前线程,将方法提交到虚拟机的线程栈中执行
VM_RevokeBias revoke(&obj, (JavaThread*) THREAD);
VMThread::execute(&revoke);
return revoke.status_code();
}
}
assert((heuristics == HR_BULK_REVOKE) ||
(heuristics == HR_BULK_REBIAS), “?”);
//当撤销偏向锁的次数达到阈值,则表示这个对象不适合偏向锁,于是对所有使用了这个对象的线程进行批量撤销或批量重偏
VM_BulkRevokeBias bulk_revoke(&obj, (JavaThread*) THREAD,
(heuristics == HR_BULK_REBIAS),
attempt_rebias);
VMThread::execute(&bulk_revoke);
return bulk_revoke.status_code();
}
接着看revoke_bias函数,是如何撤销或者重偏向锁的
static BiasedLocking::Condition revoke_bias(oop obj, bool allow_rebias, bool is_bulk, JavaThread* requesting_thread) {
markOop mark = obj->mark();
……
uint age = mark->age();
markOop biased_prototype = markOopDesc::biased_locking_prototype()->set_age(age);
markOop unbiased_prototype = markOopDesc::prototype()->set_age(age);
JavaThread* biased_thread = mark->biased_locker();
if (biased_thread == NULL) {
// 匿名偏向
if (!allow_rebias) {
obj->set_mark(unbiased_prototype);
}
if (TraceBiasedLocking && (Verbose || !is_bulk)) {
tty->print_cr(" Revoked bias of anonymously-biased object");
}
return BiasedLocking::BIAS_REVOKED;
}
// 判断线程是否存活
bool thread_is_alive = false;
if (requesting_thread == biased_thread) {
thread_is_alive = true;
} else {
for (JavaThread* cur_thread = Threads::first(); cur_thread != NULL; cur_thread = cur_thread->next()) {
if (cur_thread == biased_thread) {
thread_is_alive = true;
break;
}
}
}
//如果线程不存活,则将markword设置为匿名偏向锁或者无锁状态
if (!thread_is_alive) {
if (allow_rebias) {
obj->set_mark(biased_prototype);
} else {
obj->set_mark(unbiased_prototype);
}
if (TraceBiasedLocking && (Verbose || !is_bulk)) {
tty->print_cr(" Revoked bias of object biased toward dead thread");
}
return BiasedLocking::BIAS_REVOKED;
}
// 线程还存活则遍历线程栈中所有的Lock Record
GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(biased_thread);
BasicLock* highest_lock = NULL;
for (int i = 0; i < cached_monitor_info->length(); i++) {
MonitorInfo* mon_info = cached_monitor_info->at(i);
// 如果能找到对应的Lock Record说明偏向的线程还在执行同步代码块中的代码
if (mon_info->owner() == obj) {
if (TraceBiasedLocking && Verbose) {
tty->print_cr(" mon_info->owner (" PTR_FORMAT “) == obj (” PTR_FORMAT “)”,
(void *) mon_info->owner(),
(void *) obj);
}
// 需要升级为轻量级锁,直接修改偏向
线程栈中的Lock Record
markOop mark = markOopDesc::encode((BasicLock*) NULL);
highest_lock = mon_info->lock();
highest_lock->set_displaced_header(mark);
}
}
if (highest_lock != NULL) {
// 修改第一个Lock Record为无锁状态,然后将obj的mark word设置为指向该Lock Record的指针
highest_lock->set_displaced_header(unbiased_prototype);
// Reset object header to point to displaced mark
obj->set_mark(markOopDesc::encode(highest_lock));
assert(!obj->mark()->has_bias_pattern(), “illegal mark state: stack lock used bias bit”);
if (TraceBiasedLocking && (Verbose || !is_bulk)) {
tty->print_cr(" Revoked bias of currently-locked object");
}
} else {
// 走到这里说明偏向线程已经不在同步块中了
if (TraceBiasedLocking && (Verbose || !is_bulk)) {
tty->print_cr(" Revoked bias of currently-unlocked object");
}
if (allow_rebias) {
//设置为匿名偏向状态
obj->set_mark(biased_prototype);
} else {
// 将mark word设置为无锁状态
obj->set_mark(unbiased_prototype);
}
}
return BiasedLocking::BIAS_REVOKED;
}
了解了偏向锁的加锁流程,再接着看自旋锁和重量级锁的加锁流程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”);
//是否为无锁状态
if (mark->is_neutral()) {
//如果是无锁状态,通过cas加轻量级锁,cas成功则表示加锁成功
lock->set_displaced_header(mark);
if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
TEVENT (slow_enter: release stacklock) ;
return ;
}
} 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;
}
lock->set_displaced_header(markOopDesc::unused_mark());
//如果上面两种状态都不满足,说明出现了锁的竞争情况,轻量级锁需要膨胀成重量级锁
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}
slow_enter会判断是否是无锁,如果是,则通过CAS进行轻量级锁加锁则,如果有锁,则判断是否是同意把锁,如果是,也可以直接进入临界区,如果不是,轻量级锁需要调用flate函数膨胀成重量级锁,膨胀成重量级锁后,执行enter方法。我们先看看膨胀过程。
轻量级锁膨胀过程
我们接着看inflate是如何进行膨胀的
ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
for (;😉 {
const markOop mark = object->mark() ;
assert (!mark->has_bias_pattern(), “invariant”) ;
// 判断是否有monitor,这个monitor就是管程对象,如果已经有了管程,说明已经是重量级锁了,如果是重量级锁则退出膨胀
if (mark->has_monitor()) {
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()) {
TEVENT (Inflate: spin while INFLATING) ;
ReadStableMark(object) ;
continue ;
}
//判断是否是轻量级锁
if (mark->has_locker()) {
//获取一个可用管程,ObjectMonitor就是JVM的管程对象
ObjectMonitor * m = omAlloc (Self) ;
m->Recycle();
m->_Responsible = NULL ;
m->OwnerIsThread = 0 ;
m->_recursions = 0 ;
m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; // Consider: maintain by type/class
markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;
//CAS操作标识Mark Word正在膨胀
if (cmp != mark) {
omRelease (Self, m, true) ;
continue ;
}
//CAS成功,初始化管程ObjectMonitor的信息
markOop dmw = mark->displaced_mark_helper() ;
assert (dmw->is_neutral(), “invariant”) ;
m->set_header(dmw) ;
m->set_owner(mark->locker());
guarantee (object->mark() == markOopDesc::INFLATING(), “invariant”) ;
object->release_set_mark(markOopDesc::encode(m));
if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;
return m ;
}
……
}
}
在flate膨胀过程中,我们获取了重量级锁ObjectMonitor,这个就是JVM的管程对象,并且调用管程的enter方法,开始进入管程模型。
重量级锁加锁流程
接着看看ObjectMonitor管程的enter方法里面做了什么事情。
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 ;
……
//尝试自旋获取锁
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 ;
}
……
for (;😉 {
……
//自旋失败则进入EnterI流程。
EnterI (THREAD) ;
……
}
……
}
ObjectMonitor的enter方法中,会先通过自旋获取锁,如果自旋超过一定的次数,则自旋失败,进入EnterI流程。
void ATTR ObjectMonitor::EnterI (TRAPS) {
Thread * Self = THREAD ;
//尝试获取锁
if (TryLock (Self) > 0) {
return ;
}
//再次尝试自旋
if (TrySpin (Self) > 0) {
return ;
}
//将当前线程封装成ObjectWaiter对象
ObjectWaiter node(Self) ;
Self->_ParkEvent->reset() ;
node._prev = (ObjectWaiter *) 0xBAD ;
node.TState = ObjectWaiter::TS_CXQ ;
ObjectWaiter * nxt ;
for (;😉 {
//通过循环确保ObjectWaiter插入了队列
nod
e._next = nxt = _cxq ;
if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;
// 继续挣扎一下,尝试获取锁
if (TryLock (Self) > 0) {
return ;
}
}
if ((SyncFlags & 16) == 0 && nxt == NULL && _EntryList == NULL) {
Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
}
TEVENT (Inflated enter - Contention) ;
int nWakeups = 0 ;
int RecheckInterval = 1 ;
for (;😉 {
if (TryLock (Self) > 0) break ;
assert (_owner != Self, “invariant”) ;
if ((SyncFlags & 2) && _Responsible == NULL) {
Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
}
// park self
if (_Responsible == Self || (SyncFlags & 1)) {
//延时挂起当前线程
Self->_ParkEvent->park ((jlong) RecheckInterval) ;
RecheckInterval *= 8 ;
if (RecheckInterval > 1000) RecheckInterval = 1000 ;
} else {
TEVENT (Inflated enter - park UNTIMED) ;
//挂起当前线程
Self->_ParkEvent->park() ;
}
//依然在尝试获取锁
if (TryLock(Self) > 0) break ;
……
}
……
return ;
}
EnterI主要做了这两事情
-
将当前线程封装成ObjectWaiter,并放入等待队列
-
调用ParkEvent的park方法,ParkEvent是Thread对象的内部类,park方法会将当前线程进行挂起。
可以看到,EnterI的方法中不断的在调用tryLock尝试获取锁,主要原因也是因为将线程挂起,然后再唤醒的性能开销是比较大的,能不挂起线程就最好不挂起线程。
加锁的流程讲完了,接着来看一下释放锁的流程。
释放锁流程
释放锁的入口逻辑如下:
CASE(_monitorexit): {
//1,获取对象头
oop lockee = STACK_OBJECT(-1);
CHECK_NULL(lockee);
BasicObjectLock* limit = istate->monitor_base();
BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
while (most_recent != limit ) {
if ((most_recent)->obj() == lockee) {
BasicLock* lock = most_recent->lock();
//2,获取对象头中的markword头
markOop header = lock->displaced_header();
most_recent->set_obj(NULL);
if (header != NULL) {
if (Atomic::cmpxchg_ptr(header, lockee->mark_addr(), lock) != lock) {
// restore object for the slow case
most_recent->set_obj(lockee);
//3,执行释放锁的逻辑
CALL_VM(InterpreterRuntime::monitorexit(THREAD, most_recent), handle_exception);
}
}
UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
}
most_recent++;
}
// Need to throw illegal monitor state exception
CALL_VM(InterpreterRuntime::throw_illegal_monitor_state_exception(THREAD), handle_exception);
ShouldNotReachHere();
}
_monitorexit的入口逻辑主要调用InterpreterRuntime::monitorexit函数执行释放锁的逻辑,接着往下看
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
Handle 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 ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END
这里调用了slow_exit函数
void ObjectSynchronizer::slow_exit(oop object, BasicLock* lock, TRAPS) {
fast_exit (object, lock, THREAD) ;
}
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-op
markOop dhw = lock->displaced_header();
markOop mark ;
if (dhw == NULL) {
//重入锁,直接释放
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() ;
if (mark == (markOop) lock) {
//轻量级锁,直接释放
assert (dhw->is_neutral(), “invariant”) ;
if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {
TEVENT (fast_exit: release stacklock) ;
return;
}
}
//重量级锁释放
ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;
}
slow_exit函数里面又调用了fast_exit,这一块的调用逻辑我觉得JVM的团队需要优化一下,方法名给人很大的歧义性。fast_exit主要做了三件事
-
如果是重入锁,则直接return
-
如果是轻量级锁,则CAS重写mardword信息,释放锁
-
如果是重量级锁,通过infalte获得Monitor,然后调用Monitor的exit方法。
重量级锁释放流程
void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {
Thread * Self = THREAD ;
……
for (;😉 {
assert (THREAD == _owner, “invariant”) ;
……
ObjectWaiter * w = NULL ;
int QMode = Knob_QMode ;
if (QMode == 2 && _cxq != NULL) {
w = _cxq ;
assert (w != NULL, “invariant”) ;
assert (w->TState == ObjectWaiter::TS_CXQ, “Invariant”) ;
ExitEpilog (Self, w) ;
return ;
}
if (QMode == 3 && _cxq != NULL) {
w = _cxq ;
for (;😉 {
assert (w != NULL, “Invariant”) ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
assert (w != NULL , “invariant”) ;
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, “Invariant”) ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
// Append the RATs to the EntryList
// TODO: organize EntryList as a CDLL so we can locate the tail in constant-time.
ObjectWaiter * Tail ;
for (Tail = _EntryList ; Tail != NULL && Tail->_next != NULL ; Tail = Tail->_next) ;
if (Tail == NULL) {
_EntryList = w ;
} else {
Tail->_next = w ;
w->_prev = Tail ;
}
// Fall thru into code that tries to wake a successor from EntryList
}
if (QMode == 4 && _cxq != NULL) {
w = _cxq ;
for (;😉 {
assert (w != NULL, “Invariant”) ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
assert (w != NULL , “invariant”) ;
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, “Invariant”) ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
// Prepend the RATs to the EntryList
if (_EntryList != NULL) {
q->_next = _EntryList ;
_EntryList->_prev = q ;
}
_EntryList = w ;
// Fall thru into code that tries to wake a successor from EntryList
}
w = _EntryList ;
if (w != NULL) {
assert (w->TState == ObjectWaiter::TS_ENTER, “invariant”) ;
ExitEpilog (Self, w) ;
return ;
}
if (QMode == 1) {
// QMode == 1 : drain cxq to EntryList, reversing order
// We also reverse the order of the list.
ObjectWaiter * s = NULL ;
ObjectWaiter * t = w ;
ObjectWaiter * u = NULL ;
while (t != NULL) {
guarantee (t->TState == ObjectWaiter::TS_CXQ, “invariant”) ;
t->TState = ObjectWaiter::TS_ENTER ;
u = t->_next ;
t->_prev = u ;
t->_next = s ;
s = t;
t = u ;
}
_EntryList = s ;
assert (s != NULL, “invariant”) ;
} else {
// QMode == 0 or QMode == 2
_EntryList = w ;
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, “Invariant”) ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
}
if (_succ != NULL) continue;
w = _EntryList ;
if (w != NULL) {
guarantee (w->TState == ObjectWaiter::TS_ENTER, “invariant”) ;
ExitEpilog (Self, w) ;
return ;
}
}
}
exit函数主要的逻辑是根据QMode,也就是优先级的模式,从调用EntryList中取出一个线程,并调用ExitEpilog函数进行唤醒工作。
void ObjectMonitor::ExitEpilog (Thread * Self, ObjectWaiter * Wakee) {
assert (_owner == Self, “invariant”) ;
_succ = Knob_SuccEnabled ? Wakee->_thread : NULL ;
ParkEvent * Trigger = Wakee->_event ;
Wakee = NULL ;
// Drop the lock
OrderAccess::release_store_ptr (&_owner, NULL) ;
OrderAccess::fence() ; // ST _owner vs LD in unpark()
if (SafepointSynchronize::do_call_back()) {
TEVENT (unpark before SAFEPOINT) ;
}
//将线程唤醒
Trigger->unpark() ;
}
到这里,HotSpot版本JVM中的Synchronize实现原理已经讲完了。接下来我们再看一下Art虚拟机Synchronize的实现原理,由于他们有很多类似的地方,所以Art中的Synchronize实现逻辑不会不会非常详细的去讲,我们看一下大概流程即可。
Art中Synchronize入口和出口都在interpreter_switch_impl解释器的ExecuteSwitchImpl函数中。
/art/runtime/interpreter/interpreter_switch_impl.cc
进入管程
JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item,
ShadowFrame& shadow_frame, JValue result_register,
bool interpret_one_instruction) {
constexpr bool do_assignability_check = do_access_check;
self->VerifyStack();
uint32_t dex_pc = shadow_frame.GetDexPC();
const auto* const instrumentation = Runtime::Current()->GetInstrumentation();
const uint16_t* const insns = code_item->insns_;
const Instruction* inst = Instruction::At(insns + dex_pc);
uint16_t inst_data;
ArtMethod* method = shadow_frame.GetMethod();
jit::Jit* jit = Runtime::Current()->GetJit();
do {
dex_pc = inst->GetDexPc(insns);
shadow_frame.SetDexPC(dex_pc);
TraceExecution(shadow_frame, inst, dex_pc);
inst_data = inst->Fetch16(0);
switch (inst->Opcode(inst_data)) {
……
//Synchronize入口
case Instruction::MONITOR_ENTER: {
PREAMBLE();
ObjPtrmirror::Object obj = shadow_frame.GetVRegReference(inst->VRegA_11x(inst_data));
if (UNLIKELY(obj == nullptr)) {
ThrowNullPointerExceptionFromInterpreter();
HANDLE_PENDING_EXCEPTION();
} else {
DoMonitorEnter<do_assignability_check>(self, &shadow_frame, obj);
POSSIBLY_HANDLE_PENDING_EXCEPTION(self->IsExceptionPending(), Next_1xx);
}
break;
}
//Synchronize出口
case Instruction::MONITOR_EXIT: {
PREAMBLE();
ObjPtrmirror::Object obj = shadow_frame.GetVRegReference(inst->VRegA_11x(inst_data));
if (UNLIKELY(obj == nullptr)) {
ThrowNullPointerExceptionFromInterpreter();
HANDLE_PENDING_EXCEPTION();
} else {
DoMonitorExit<do_assignability_check>(self, &shadow_frame, obj);
POSSIBLY_HANDLE_PENDING_EXCEPTION(self->IsExceptionPending(), Next_1xx);
}
break;
}
……
}
} while (!interpret_one_instruction);
// Record where we stopped.
shadow_frame.SetDexPC(inst->GetDexPc(insns));
return result_register;
} // NOLINT(readability/fn_siz
先看Art是如何加锁的,它的处理逻辑再DoMonitorEnter函数中
加锁流程
/art/runtime/interpreter/interpreter_common.h
static inline void DoMonitorEnter(Thread* self, ShadowFrame* frame, ObjPtrmirror::Object ref)
NO_THREAD_SAFETY_ANALYSIS
REQUIRES(!Roles::uninterruptible_) {
StackHandleScope<1> hs(self);
Handlemirror::Object h_ref(hs.NewHandle(ref));
h_ref->MonitorEnter(self);
if (kMonitorCounting && frame->GetMethod()->MustCountLocks()) {
frame->GetLockCountData().AddMonitor(self, h_ref.Get());
}
}
里面最终调用了MonitorEnter函数。
/art/runtime/monitor.cc
mirror::Object* Monitor::MonitorEnter(Thread* self, mirror::Object* obj, bool trylock) {
DCHECK(self != nullptr);
DCHECK(obj != nullptr);
self->AssertThreadSuspensionIsAllowable();
obj = FakeLock(obj);
uint32_t thread_id = self->GetThreadId();
size_t contention_count = 0;
StackHandleScope<1> hs(self);
Handlemirror::Object h_obj(hs.NewHandle(obj));
while (true) {
//获取LockWord,类似JVM中的MarkWord
LockWord lock_word = h_obj->GetLockWord(false);
switch (lock_word.GetState()) {
case LockWord::kUnlocked: {
//无锁状态,则进行轻量级锁的加锁过程
LockWord thin_locked(LockWord::FromThinLockId(thread_id, 0, lock_word.GCState()));
if (h_obj->CasLockWordWeakAcquire(lock_word, thin_locked)) {
AtraceMonitorLock(self, h_obj.Get(), false /* is_wait */);
return h_obj.Get(); // Success!
}
continue; // Go again.
}
case LockWord::kThinLocked: {
//轻量级锁
uint32_t owner_thread_id = lock_word.ThinLockOwner();
// 判断lock_word的threadid是否相等
if (owner_thread_id == thread_id){
uint32_t new_count = lock_word.ThinLockCount() + 1;
if (LIKELY(new_count <= LockWord::kThinLockMaxCount)) {
//如果相等,并且小于轻量级锁的最大数量,则直接进入临界区
LockWord thin_locked(LockWord::FromThinLockId(thread_id,
new_count,
lock_word.GCState()));
if (!kUseReadBarrier) {
h_obj->SetLockWord(thin_locked, false /* volatile */);
AtraceMonitorLock(self, h_obj.Get(), false /* is_wait */);
return h_obj.Get(); // Success!
} else {
// Use CAS to preserve the read barrier state.
if (h_obj->CasLockWordWeakRelaxed(lock_word, thin_locked)) {
AtraceMonitorLock(self, h_obj.Get(), false /* is_wait */);
return h_obj.Get(); // Success!
}
}
continue; // Go again.
} else {
// 如果超出轻量级锁的数量,则进行重量级锁的加锁流程
InflateThinLocked(self, h_obj, lock_word, 0);
}
} else {
//lock_word的threadid和当前线程的threadid不等,升级成重量级锁并加锁
if (trylock) {
return nullptr;
}
// Contention.
contention_count++;
Runtime* runtime = Runtime::Current();
if (contention_count <= runtime->GetMaxSpinsBeforeThinLockInflation()) {
sched_yield();
} else {
contention_count = 0;
// No ordering required for initial lockword read. Install rereads it anyway.
InflateThinLocked(self, h_obj, lock_word, 0);
}
}
continue; // Start from the beginning.
}
case LockWord::kFatLocked: {
//重量级锁
QuasiAtomic::ThreadFenceAcquire();
//获取monitor
Monitor* mon = lock_word.FatLockMonitor()
//尝试获取锁
if (trylock) {
return mon->TryLock(self) ? h_obj.Get() : nullptr;
} else {
mon->Lock(self);
return h_obj.Get(); // Success!
}
}
default: {
LOG(FATAL) << "Invalid monitor state " << lock_word.GetState();
UNREACHABLE();
}
}
}
}
MonitorEnter函数中做的事情主要如下:
-
如果是无锁,通过CAS加轻量级锁,调用continue,进入步骤2。
-
如果是轻量级锁,判断LockWord的threadId和当前线程的ThreadId是否相等,如果相等,并且轻量级锁没有超过最大限制的情况下,return退出循环,进入临界区。如果不相等,则通过InflateThinLocked升级成重量级锁,升级的过程主要是创建和初始化Monitor的过程,调用continue,进入不走3。
-
如果是重量级锁,通过调用Monitor的lock函数获取锁
接着看lock获取锁的过程
/art/runtime/monitor.cc
void Monitor::Lock(Thread* self) {
MutexLock mu(self, monitor_lock_);
while (true) {
//尝试获取锁
if (TryLockLocked(self)) {
return;
}
// Contended.
const bool log_contention = (lock_profiling_threshold_ != 0);
uint64_t wait_start_ms = log_contention ? MilliTime() : 0;
ArtMethod* owners_method = locking_method_;
uint32_t owners_dex_pc = locking_dex_pc_;
// Do this before releasing the lock so that we don’t get deflated.
size_t num_waiters = num_waiters_;
++num_waiters_;
monitor_lock_.Unlock(self); // Let go of locks in order.
self->SetMonitorEnterObject(GetObject());
{
ScopedThreadSuspension tsc(self, kBlocked); // Change to blocked and give up mutator_lock_.
uint32_t original_owner_thread_id = 0u;
{
// Reacquire monitor_lock_ without mutator_lock_ for Wait.
MutexLock mu2(self, monitor_lock_);
if (owner_ != nullptr) { // Did the owner_ give the lock up?
original_owner_thread_id = owner_->GetThreadId();
//调用Wait函数,将线程休眠
monitor_contenders_.Wait(self);
}
……
}
}
self->SetMonitorEnterObject(nullptr);
monitor_lock_.Lock(self); // Reacquire locks in order.
–num_waiters_;
}
}
Lock函数主要在自旋中通过TryLockLocked获取锁,获取不到的情况下调用Wait将线程休眠。接着看一下Wait是如何休眠线程的。
/art/runtime/monitor.cc
void Monitor::Wait(Thread* self, int64_t ms, int32_t ns,
bool interruptShouldThrow, ThreadState why) {
……
) << "Invalid monitor state " << lock_word.GetState();
UNREACHABLE();
}
}
}
}
MonitorEnter函数中做的事情主要如下:
-
如果是无锁,通过CAS加轻量级锁,调用continue,进入步骤2。
-
如果是轻量级锁,判断LockWord的threadId和当前线程的ThreadId是否相等,如果相等,并且轻量级锁没有超过最大限制的情况下,return退出循环,进入临界区。如果不相等,则通过InflateThinLocked升级成重量级锁,升级的过程主要是创建和初始化Monitor的过程,调用continue,进入不走3。
-
如果是重量级锁,通过调用Monitor的lock函数获取锁
接着看lock获取锁的过程
/art/runtime/monitor.cc
void Monitor::Lock(Thread* self) {
MutexLock mu(self, monitor_lock_);
while (true) {
//尝试获取锁
if (TryLockLocked(self)) {
return;
}
// Contended.
const bool log_contention = (lock_profiling_threshold_ != 0);
uint64_t wait_start_ms = log_contention ? MilliTime() : 0;
ArtMethod* owners_method = locking_method_;
uint32_t owners_dex_pc = locking_dex_pc_;
// Do this before releasing the lock so that we don’t get deflated.
size_t num_waiters = num_waiters_;
++num_waiters_;
monitor_lock_.Unlock(self); // Let go of locks in order.
self->SetMonitorEnterObject(GetObject());
{
ScopedThreadSuspension tsc(self, kBlocked); // Change to blocked and give up mutator_lock_.
uint32_t original_owner_thread_id = 0u;
{
// Reacquire monitor_lock_ without mutator_lock_ for Wait.
MutexLock mu2(self, monitor_lock_);
if (owner_ != nullptr) { // Did the owner_ give the lock up?
original_owner_thread_id = owner_->GetThreadId();
//调用Wait函数,将线程休眠
monitor_contenders_.Wait(self);
}
……
}
}
self->SetMonitorEnterObject(nullptr);
monitor_lock_.Lock(self); // Reacquire locks in order.
–num_waiters_;
}
}
Lock函数主要在自旋中通过TryLockLocked获取锁,获取不到的情况下调用Wait将线程休眠。接着看一下Wait是如何休眠线程的。
/art/runtime/monitor.cc
void Monitor::Wait(Thread* self, int64_t ms, int32_t ns,
bool interruptShouldThrow, ThreadState why) {
……
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
相关文章
- 企业数据资产盘点与数据标准梳理方法
大数据在中国的大地上蓬勃发展,带动了许多新的理念的诞生,数据治理就是其中之一,这两年已建立好的数据中心或者正在建的数据中心都在如火如荼的进行,在企业内部,也已经开始由原来的业务部门和IT部门演变出来数据管理部…...
2024/4/14 3:04:21 - Centos8 x11vnc以及图形化安装
# ################################################################# # 脚本名称 : Centos8.5.2111 X11VNC # 脚本描述 : 自动安装配置和自动启动X11Vnc # 编辑日期 :2022.01.11 # 编写日期 : 2022.01.11 # 原 作 者 : Wayne Xu # 软件版本 : 0.1 # …...
2024/4/14 3:04:11 - OpenHarmony轻量系统开发【3】代码编译和烧录
摘要:本文简单介绍OpenHarmony最新版本代码目录简单解读、编译、烧录 适合群体:适用于Hi3861开发板 3.1源码目录 下载完代码后,大家可以进入代码目录: 这里重点介绍几个比较重要的文件夹: 1 vendor文件夹 该文件夹…...
2024/4/14 3:04:41 - 掌握Android和Java线程原理上,阿里架构师深入讲解Android开发
child_thread nullptr; // TODO: remove from thread group? env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer, 0); { std::string msg(child_jni_env_ext.get() nullptr ? StringPrintf(“Could not allocate JNI Env: %s”, error_…...
2024/5/5 19:29:19 - 【Android春招每日一练】(十三) 剑指4题+Android基础
文章目录概览剑指offer1.49 第一个只出现一次的字符1.50 数组中的逆序对1.51 两个链表的第一个公共节点1.52 在排序数组中查找数字 IAndroid基础Android IPC几种方式Bitmap压缩策略Android动画总结进程优先级总结概览 剑指offer:第一个只出现一次的字符、数组中的逆…...
2024/5/5 18:17:33 - Java依赖冲突高效解决之道
一 概述 由于阿里妈妈联盟团队负责业务的特殊性,系统有庞大的对外依赖,依赖集团六七十个团队服务及N多工具组件,通过此文和大家分享一下我们积累的一些复杂依赖有效治理的经验,除了简单技术技巧的总结外,也会探讨一些…...
2024/4/14 3:04:31 - L2-023 图着色问题 (25 分)
L2-023 图着色问题 (25 分) 图着色问题是一个著名的NP完全问题。给定无向图G(V,E),问可否用K种颜色为V中的每一个顶点分配一种颜色,使得不会有两个相邻顶点具有同一种颜色? 但本题并不是要你解决这个着色问题,而是对给定的一种颜…...
2024/4/5 4:01:42 - 【IP路由基础(直连路由、静态路由的三种配置)】【上】--20211203
目录 一、三层路由的产生背景 1.网段之间的通信 2.路由 1.路由信息 基本路由思想: 2.路由表 路径信息(路由信息): 3.路由来源 1)直连路由 1.路由优先级(取值越小越优先) 2.开销值&a…...
2024/4/14 3:04:11 - Web语义化(HTML5语义化标签)
转载文章:https://www.cnblogs.com/weixiao-he/p/8630084.html 做了部分补充 什么是Web语义化 Web语义化是指使用恰当语义的html标签、class类名等内容,让页面具有良好的结构与含义,从而让人和机器都能快速理解网页内容。包含了html标签语义…...
2024/4/7 17:34:23 - Raft协议图解,缺陷以及优化
结点的状态 每个结点可以有三种状态:Follower,Candidate,Leader。所有的结点都是从Follower状态开始的 如果followers没有收到leader的RPC消息(心跳),则可以转换为candidate,如下图node a&…...
2024/4/19 8:35:53 - Python必看书单
Python必看书单 学习和使用 Python 这么多年,我买了很多书,也看了很多技术书,后来发现书不在于多,而在于经典。 读经典的书是站在巨人的肩膀上,看得远、悟得深~,说实话我这些年的技术进展都是靠这些书籍的…...
2024/4/26 0:27:34 - OpenHarmony轻量系统开发【2】源码下载和开发环境
摘要:本文简单介绍OpenHarmony开发环境,代码下载、版本更新日志等。 适合群体:想要上手开发OpenHarmony设备 2.1源码下载 关于源码下载的,读者可以直接查看官网: https://gitee.com/openharmony/docs/blob/master/zh…...
2024/5/5 16:29:50 - DM8在银河麒麟V10下的安装
1、软件版本介绍 操作系统:银河麒麟v10 sp2 数据库:DM8企业版 2、规划安装用户和组: [rootKylinDCA03 opt]# groupadd dinstall [rootKylinDCA03 opt]# useradd -g dinstall -m -d /home/dmdba -s /bin/bash dmdba [rootKylinDCA03 opt]#…...
2024/4/17 9:38:16 - Spring Transactional
有7种,分3类 死活不要事务 NEVER, NOT_SUPPORTED 可有可无的 SUPORTS 有好几种不同的transactionManager,现在公司用的hibernate transactionInterceptor继承自methodInterceptor,methodInterceptor是一个aop的拦截器,调用invo…...
2024/4/17 21:58:41 - WinCC读取串口扫描枪案例
目录 一 MSComm控件注册 二 准备微软脚本调试器 三 WinCC画面制作 四 创建全局脚本VBS 五 项目启动前的配置 六 关于USB扫描枪和串口扫描枪 本文以WinCC通过VBS脚本进行串口通讯,以读取扫描枪为例进行测试 参考资料: MScomm控件注册方法…...
2024/4/14 3:06:18 - 力扣解题:罗马数字转整数
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如…...
2024/4/7 17:34:14 - EXCEL的交集和并集操作
交集运算符 空格,可以是 EXCEL交集运算符G16:G18 G18:G20G18得内容如果两者没有交集会返回空!SUM(G10:G12 I10:I12) #NULL! 或 G10:G12 I10:I12 #NULL! 并集呢 也不能用VBA的 union()...
2024/5/5 8:36:44 - InnoDB数据页结构
前言 之前说过,记录是数据库操作的基本单位(如不理解请看InnoDB记录结构),数据存储在磁盘中,数据库操作在内存中进行,要完成对数据库的操作就要进行磁盘和内存的交互,但是如果数据众多…...
2024/4/18 13:07:59 - 2022-01-26
从 Vertex 到 Subgraph 再到 PIE: 并行图计算编程模型概览 对于图计算来说,一个基础性的问题是如何选取合适的编程模型。它不仅决定了用户上手的难易程度,还对系统的整体性能有很大的影响。在本篇文章中,我们将介绍三种常见的并行图计算编程模…...
2024/4/14 3:05:27 - 快速查询的秘籍 —— B+索引的建立
前言 之前的文章说过,InnoDB存储数据的单位是页,各个数据页之间采用双向链表连接起来;而每个数据页中的记录之间又通过单向链表连接,而记录又分为若干个组,组采用最大记录的记录信息作为槽信息,多个槽组合…...
2024/4/7 17:34:10
最新文章
- spring框架学习记录(3)
Spring事务 Spring事务简介 事务作用:在数据层保障一系列的数据库操作同成功同失败Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功或同失败 Spring事务角色 事务管理员:发起事务方,在Spring中通常指代业务层开…...
2024/5/5 22:21:25 - 梯度消失和梯度爆炸的一些处理方法
在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言,在此感激不尽。 权重和梯度的更新公式如下: w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...
2024/3/20 10:50:27 - 同一个pdf在windows和linux中的页数不一样
之前认为PDF的格式,至少页数是不会变化的,结果最近发现一个文档在windows和linux中的页数不一样,linux中的pdf进入像word一样排版变得紧凑了,原本在下一页的几行进入了上一页的末尾。问了gpt后得到这样的回答: PDF文档…...
2024/5/5 6:48:14 - 数据结构——二叉树——二叉搜索树(Binary Search Tree, BST)
目录 一、98. 验证二叉搜索树 二、96. 不同的二叉搜索树 三、538. 把二叉搜索树转换为累加树 二叉搜索树:对于二叉搜索树中的每个结点,其左子结点的值小于该结点的值,而右子结点的值大于该结点的值 一、98. 验证二叉搜索树 给你一个二叉树的…...
2024/5/5 17:26:06 - 【外汇早评】美通胀数据走低,美元调整
原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...
2024/5/4 23:54:56 - 【原油贵金属周评】原油多头拥挤,价格调整
原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...
2024/5/4 23:54:56 - 【外汇周评】靓丽非农不及疲软通胀影响
原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...
2024/5/4 23:54:56 - 【原油贵金属早评】库存继续增加,油价收跌
原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...
2024/5/4 23:55:17 - 【外汇早评】日本央行会议纪要不改日元强势
原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...
2024/5/4 23:54:56 - 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响
原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...
2024/5/4 23:55:05 - 【外汇早评】美欲与伊朗重谈协议
原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...
2024/5/4 23:54:56 - 【原油贵金属早评】波动率飙升,市场情绪动荡
原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...
2024/5/4 23:55:16 - 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试
原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...
2024/5/4 23:54:56 - 【原油贵金属早评】市场情绪继续恶化,黄金上破
原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...
2024/5/4 18:20:48 - 【外汇早评】美伊僵持,风险情绪继续升温
原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...
2024/5/4 23:54:56 - 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势
原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...
2024/5/4 23:55:17 - 氧生福地 玩美北湖(上)——为时光守候两千年
原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...
2024/5/4 23:55:06 - 氧生福地 玩美北湖(中)——永春梯田里的美与鲜
原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...
2024/5/4 23:54:56 - 氧生福地 玩美北湖(下)——奔跑吧骚年!
原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...
2024/5/4 23:55:06 - 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!
原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...
2024/5/5 8:13:33 - 「发现」铁皮石斛仙草之神奇功效用于医用面膜
原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...
2024/5/4 23:55:16 - 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者
原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...
2024/5/4 23:54:58 - 广州械字号面膜生产厂家OEM/ODM4项须知!
原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...
2024/5/4 23:55:01 - 械字号医用眼膜缓解用眼过度到底有无作用?
原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...
2024/5/4 23:54:56 - 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...
解析如下:1、长按电脑电源键直至关机,然后再按一次电源健重启电脑,按F8健进入安全模式2、安全模式下进入Windows系统桌面后,按住“winR”打开运行窗口,输入“services.msc”打开服务设置3、在服务界面,选中…...
2022/11/19 21:17:18 - 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。
%读入6幅图像(每一幅图像的大小是564*564) 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 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...
win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面,在等待界面中我们需要等待操作结束才能关机,虽然这比较麻烦,但是对系统进行配置和升级…...
2022/11/19 21:17:15 - 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...
有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows,请勿关闭计算机”的提示,要过很久才能进入系统,有的用户甚至几个小时也无法进入,下面就教大家这个问题的解决方法。第一种方法:我们首先在左下角的“开始…...
2022/11/19 21:17:14 - win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...
置信有很多用户都跟小编一样遇到过这样的问题,电脑时发现开机屏幕显现“正在配置Windows Update,请勿关机”(如下图所示),而且还需求等大约5分钟才干进入系统。这是怎样回事呢?一切都是正常操作的,为什么开时机呈现“正…...
2022/11/19 21:17:13 - 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...
Win7系统开机启动时总是出现“配置Windows请勿关机”的提示,没过几秒后电脑自动重启,每次开机都这样无法进入系统,此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一:开机按下F8,在出现的Windows高级启动选…...
2022/11/19 21:17:12 - 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...
有不少windows10系统用户反映说碰到这样一个情况,就是电脑提示正在准备windows请勿关闭计算机,碰到这样的问题该怎么解决呢,现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法:1、2、依次…...
2022/11/19 21:17:11 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...
今天和大家分享一下win7系统重装了Win7旗舰版系统后,每次关机的时候桌面上都会显示一个“配置Windows Update的界面,提示请勿关闭计算机”,每次停留好几分钟才能正常关机,导致什么情况引起的呢?出现配置Windows Update…...
2022/11/19 21:17:10 - 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...
只能是等着,别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚,只能是考虑备份数据后重装系统了。解决来方案一:管理员运行cmd:net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...
2022/11/19 21:17:09 - 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?
原标题:电脑提示“配置Windows Update请勿关闭计算机”怎么办?win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢?一般的方…...
2022/11/19 21:17:08 - 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...
关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!关机提示 windows7 正在配…...
2022/11/19 21:17:05 - 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...
钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...
2022/11/19 21:17:05 - 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...
前几天班里有位学生电脑(windows 7系统)出问题了,具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面,长时间没反应,无法进入系统。这个问题原来帮其他同学也解决过,网上搜了不少资料&#x…...
2022/11/19 21:17:04 - 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...
本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法,并在最后教给你1种保护系统安全的好方法,一起来看看!电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中,添加了1个新功能在“磁…...
2022/11/19 21:17:03 - 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...
许多用户在长期不使用电脑的时候,开启电脑发现电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机。。.这要怎么办呢?下面小编就带着大家一起看看吧!如果能够正常进入系统,建议您暂时移…...
2022/11/19 21:17:02 - 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...
配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!配置windows update失败 还原更改 请勿关闭计算机&#x…...
2022/11/19 21:17:01 - 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...
不知道大家有没有遇到过这样的一个问题,就是我们的win7系统在关机的时候,总是喜欢显示“准备配置windows,请勿关机”这样的一个页面,没有什么大碍,但是如果一直等着的话就要两个小时甚至更久都关不了机,非常…...
2022/11/19 21:17:00 - 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...
当电脑出现正在准备配置windows请勿关闭计算机时,一般是您正对windows进行升级,但是这个要是长时间没有反应,我们不能再傻等下去了。可能是电脑出了别的问题了,来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...
2022/11/19 21:16:59 - 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...
我们使用电脑的过程中有时会遇到这种情况,当我们打开电脑之后,发现一直停留在一个界面:“配置Windows Update失败,还原更改请勿关闭计算机”,等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢࿰…...
2022/11/19 21:16:58 - 如何在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