Skip to content

Commit 99eac53

Browse files
author
David Holmes
committed
8225631: Consider replacing muxAcquire/Release with PlatformMonitor
Reviewed-by: coleenp, dcubed, kbarrett
1 parent 646c200 commit 99eac53

File tree

6 files changed

+26
-183
lines changed

6 files changed

+26
-183
lines changed

src/hotspot/os/posix/os_posix.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1520,7 +1520,7 @@ void os::PlatformEvent::unpark() {
15201520
// shake out uses of park() and unpark() without checking state conditions
15211521
// properly. This spurious return doesn't manifest itself in any user code
15221522
// but only in the correctly written condition checking loops of ObjectMonitor,
1523-
// Mutex/Monitor, Thread::muxAcquire and JavaThread::sleep
1523+
// Mutex/Monitor, and JavaThread::sleep
15241524

15251525
if (Atomic::xchg(&_event, 1) >= 0) return;
15261526

src/hotspot/share/runtime/park.hpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -125,7 +125,6 @@ class ParkEvent : public os::PlatformEvent {
125125
public:
126126
// MCS-CLH list linkage and Native Mutex/Monitor
127127
ParkEvent * volatile ListNext ;
128-
volatile intptr_t OnList ;
129128
volatile int TState ;
130129
volatile int Notified ; // for native monitor construct
131130

@@ -146,7 +145,6 @@ class ParkEvent : public os::PlatformEvent {
146145
AssociatedWith = NULL ;
147146
FreeNext = NULL ;
148147
ListNext = NULL ;
149-
OnList = 0 ;
150148
TState = 0 ;
151149
Notified = 0 ;
152150
}

src/hotspot/share/runtime/synchronizer.cpp

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,14 @@ int dtrace_waited_probe(ObjectMonitor* monitor, Handle obj, Thread* thr) {
227227
return 0;
228228
}
229229

230-
#define NINFLATIONLOCKS 256
231-
static volatile intptr_t gInflationLocks[NINFLATIONLOCKS];
230+
static const int NINFLATIONLOCKS = 256;
231+
static os::PlatformMutex* gInflationLocks[NINFLATIONLOCKS];
232+
233+
void ObjectSynchronizer::initialize() {
234+
for (int i = 0; i < NINFLATIONLOCKS; i++) {
235+
gInflationLocks[i] = new os::PlatformMutex();
236+
}
237+
}
232238

233239
static MonitorList _in_use_list;
234240
// The ratio of the current _in_use_list count to the ceiling is used
@@ -749,13 +755,7 @@ static markWord read_stable_mark(oop obj) {
749755

750756
// The object is being inflated by some other thread.
751757
// The caller of read_stable_mark() must wait for inflation to complete.
752-
// Avoid live-lock
753-
// TODO: consider calling SafepointSynchronize::do_call_back() while
754-
// spinning to see if there's a safepoint pending. If so, immediately
755-
// yielding or blocking would be appropriate. Avoid spinning while
756-
// there is a safepoint pending.
757-
// TODO: add inflation contention performance counters.
758-
// TODO: restrict the aggregate number of spinners.
758+
// Avoid live-lock.
759759

760760
++its;
761761
if (its > 10000 || !os::is_MP()) {
@@ -775,15 +775,15 @@ static markWord read_stable_mark(oop obj) {
775775
// and calling park(). When inflation was complete the thread that accomplished inflation
776776
// would detach the list and set the markword to inflated with a single CAS and
777777
// then for each thread on the list, set the flag and unpark() the thread.
778-
// This is conceptually similar to muxAcquire-muxRelease, except that muxRelease
779-
// wakes at most one thread whereas we need to wake the entire list.
778+
779+
// Index into the lock array based on the current object address.
780+
static_assert(is_power_of_2(NINFLATIONLOCKS), "must be");
780781
int ix = (cast_from_oop<intptr_t>(obj) >> 5) & (NINFLATIONLOCKS-1);
781782
int YieldThenBlock = 0;
782783
assert(ix >= 0 && ix < NINFLATIONLOCKS, "invariant");
783-
assert((NINFLATIONLOCKS & (NINFLATIONLOCKS-1)) == 0, "invariant");
784-
Thread::muxAcquire(gInflationLocks + ix, "gInflationLock");
784+
gInflationLocks[ix]->lock();
785785
while (obj->mark() == markWord::INFLATING()) {
786-
// Beware: NakedYield() is advisory and has almost no effect on some platforms
786+
// Beware: naked_yield() is advisory and has almost no effect on some platforms
787787
// so we periodically call self->_ParkEvent->park(1).
788788
// We use a mixed spin/yield/block mechanism.
789789
if ((YieldThenBlock++) >= 16) {
@@ -792,7 +792,7 @@ static markWord read_stable_mark(oop obj) {
792792
os::naked_yield();
793793
}
794794
}
795-
Thread::muxRelease(gInflationLocks + ix);
795+
gInflationLocks[ix]->unlock();
796796
}
797797
} else {
798798
SpinPause(); // SMP-polite spinning

src/hotspot/share/runtime/synchronizer.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "oops/markWord.hpp"
3030
#include "runtime/basicLock.hpp"
3131
#include "runtime/handles.hpp"
32+
#include "runtime/os.hpp"
3233
#include "runtime/perfData.hpp"
3334

3435
class LogStream;
@@ -114,6 +115,9 @@ class ObjectSynchronizer : AllStatic {
114115
static void release_monitors_owned_by_thread(TRAPS);
115116
static void monitors_iterate(MonitorClosure* m);
116117

118+
// Initialize the gInflationLocks
119+
static void initialize();
120+
117121
// GC: we current use aggressive monitor deflation policy
118122
// Basically we try to deflate all monitors that are not busy.
119123
static size_t deflate_idle_monitors();

src/hotspot/share/runtime/thread.cpp

Lines changed: 2 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,6 @@ Thread::Thread() {
291291
// The stack would act as a cache to avoid calls to ParkEvent::Allocate()
292292
// and ::Release()
293293
_ParkEvent = ParkEvent::Allocate(this);
294-
_MuxEvent = ParkEvent::Allocate(this);
295294

296295
#ifdef CHECK_UNHANDLED_OOPS
297296
if (CheckUnhandledOops) {
@@ -439,7 +438,6 @@ Thread::~Thread() {
439438
// It's possible we can encounter a null _ParkEvent, etc., in stillborn threads.
440439
// We NULL out the fields for good hygiene.
441440
ParkEvent::Release(_ParkEvent); _ParkEvent = NULL;
442-
ParkEvent::Release(_MuxEvent); _MuxEvent = NULL;
443441

444442
delete handle_area();
445443
delete metadata_handles();
@@ -3560,6 +3558,7 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
35603558

35613559
// Initialize Java-Level synchronization subsystem
35623560
ObjectMonitor::Initialize();
3561+
ObjectSynchronizer::initialize();
35633562

35643563
// Initialize global modules
35653564
jint status = init_globals();
@@ -4582,22 +4581,11 @@ void Threads::print_threads_compiling(outputStream* st, char* buf, int buflen, b
45824581
}
45834582

45844583

4585-
// Internal SpinLock and Mutex
4586-
// Based on ParkEvent
4587-
4588-
// Ad-hoc mutual exclusion primitives: SpinLock and Mux
4584+
// Ad-hoc mutual exclusion primitives: SpinLock
45894585
//
45904586
// We employ SpinLocks _only for low-contention, fixed-length
45914587
// short-duration critical sections where we're concerned
45924588
// about native mutex_t or HotSpot Mutex:: latency.
4593-
// The mux construct provides a spin-then-block mutual exclusion
4594-
// mechanism.
4595-
//
4596-
// Testing has shown that contention on the ListLock guarding gFreeList
4597-
// is common. If we implement ListLock as a simple SpinLock it's common
4598-
// for the JVM to devolve to yielding with little progress. This is true
4599-
// despite the fact that the critical sections protected by ListLock are
4600-
// extremely short.
46014589
//
46024590
// TODO-FIXME: ListLock should be of type SpinLock.
46034591
// We should make this a 1st-class type, integrated into the lock
@@ -4650,150 +4638,6 @@ void Thread::SpinRelease(volatile int * adr) {
46504638
*adr = 0;
46514639
}
46524640

4653-
// muxAcquire and muxRelease:
4654-
//
4655-
// * muxAcquire and muxRelease support a single-word lock-word construct.
4656-
// The LSB of the word is set IFF the lock is held.
4657-
// The remainder of the word points to the head of a singly-linked list
4658-
// of threads blocked on the lock.
4659-
//
4660-
// * The current implementation of muxAcquire-muxRelease uses its own
4661-
// dedicated Thread._MuxEvent instance. If we're interested in
4662-
// minimizing the peak number of extant ParkEvent instances then
4663-
// we could eliminate _MuxEvent and "borrow" _ParkEvent as long
4664-
// as certain invariants were satisfied. Specifically, care would need
4665-
// to be taken with regards to consuming unpark() "permits".
4666-
// A safe rule of thumb is that a thread would never call muxAcquire()
4667-
// if it's enqueued (cxq, EntryList, WaitList, etc) and will subsequently
4668-
// park(). Otherwise the _ParkEvent park() operation in muxAcquire() could
4669-
// consume an unpark() permit intended for monitorenter, for instance.
4670-
// One way around this would be to widen the restricted-range semaphore
4671-
// implemented in park(). Another alternative would be to provide
4672-
// multiple instances of the PlatformEvent() for each thread. One
4673-
// instance would be dedicated to muxAcquire-muxRelease, for instance.
4674-
//
4675-
// * Usage:
4676-
// -- Only as leaf locks
4677-
// -- for short-term locking only as muxAcquire does not perform
4678-
// thread state transitions.
4679-
//
4680-
// Alternatives:
4681-
// * We could implement muxAcquire and muxRelease with MCS or CLH locks
4682-
// but with parking or spin-then-park instead of pure spinning.
4683-
// * Use Taura-Oyama-Yonenzawa locks.
4684-
// * It's possible to construct a 1-0 lock if we encode the lockword as
4685-
// (List,LockByte). Acquire will CAS the full lockword while Release
4686-
// will STB 0 into the LockByte. The 1-0 scheme admits stranding, so
4687-
// acquiring threads use timers (ParkTimed) to detect and recover from
4688-
// the stranding window. Thread/Node structures must be aligned on 256-byte
4689-
// boundaries by using placement-new.
4690-
// * Augment MCS with advisory back-link fields maintained with CAS().
4691-
// Pictorially: LockWord -> T1 <-> T2 <-> T3 <-> ... <-> Tn <-> Owner.
4692-
// The validity of the backlinks must be ratified before we trust the value.
4693-
// If the backlinks are invalid the exiting thread must back-track through the
4694-
// the forward links, which are always trustworthy.
4695-
// * Add a successor indication. The LockWord is currently encoded as
4696-
// (List, LOCKBIT:1). We could also add a SUCCBIT or an explicit _succ variable
4697-
// to provide the usual futile-wakeup optimization.
4698-
// See RTStt for details.
4699-
//
4700-
4701-
4702-
const intptr_t LOCKBIT = 1;
4703-
4704-
void Thread::muxAcquire(volatile intptr_t * Lock, const char * LockName) {
4705-
intptr_t w = Atomic::cmpxchg(Lock, (intptr_t)0, LOCKBIT);
4706-
if (w == 0) return;
4707-
if ((w & LOCKBIT) == 0 && Atomic::cmpxchg(Lock, w, w|LOCKBIT) == w) {
4708-
return;
4709-
}
4710-
4711-
ParkEvent * const Self = Thread::current()->_MuxEvent;
4712-
assert((intptr_t(Self) & LOCKBIT) == 0, "invariant");
4713-
for (;;) {
4714-
int its = (os::is_MP() ? 100 : 0) + 1;
4715-
4716-
// Optional spin phase: spin-then-park strategy
4717-
while (--its >= 0) {
4718-
w = *Lock;
4719-
if ((w & LOCKBIT) == 0 && Atomic::cmpxchg(Lock, w, w|LOCKBIT) == w) {
4720-
return;
4721-
}
4722-
}
4723-
4724-
Self->reset();
4725-
Self->OnList = intptr_t(Lock);
4726-
// The following fence() isn't _strictly necessary as the subsequent
4727-
// CAS() both serializes execution and ratifies the fetched *Lock value.
4728-
OrderAccess::fence();
4729-
for (;;) {
4730-
w = *Lock;
4731-
if ((w & LOCKBIT) == 0) {
4732-
if (Atomic::cmpxchg(Lock, w, w|LOCKBIT) == w) {
4733-
Self->OnList = 0; // hygiene - allows stronger asserts
4734-
return;
4735-
}
4736-
continue; // Interference -- *Lock changed -- Just retry
4737-
}
4738-
assert(w & LOCKBIT, "invariant");
4739-
Self->ListNext = (ParkEvent *) (w & ~LOCKBIT);
4740-
if (Atomic::cmpxchg(Lock, w, intptr_t(Self)|LOCKBIT) == w) break;
4741-
}
4742-
4743-
while (Self->OnList != 0) {
4744-
Self->park();
4745-
}
4746-
}
4747-
}
4748-
4749-
// Release() must extract a successor from the list and then wake that thread.
4750-
// It can "pop" the front of the list or use a detach-modify-reattach (DMR) scheme
4751-
// similar to that used by ParkEvent::Allocate() and ::Release(). DMR-based
4752-
// Release() would :
4753-
// (A) CAS() or swap() null to *Lock, releasing the lock and detaching the list.
4754-
// (B) Extract a successor from the private list "in-hand"
4755-
// (C) attempt to CAS() the residual back into *Lock over null.
4756-
// If there were any newly arrived threads and the CAS() would fail.
4757-
// In that case Release() would detach the RATs, re-merge the list in-hand
4758-
// with the RATs and repeat as needed. Alternately, Release() might
4759-
// detach and extract a successor, but then pass the residual list to the wakee.
4760-
// The wakee would be responsible for reattaching and remerging before it
4761-
// competed for the lock.
4762-
//
4763-
// Both "pop" and DMR are immune from ABA corruption -- there can be
4764-
// multiple concurrent pushers, but only one popper or detacher.
4765-
// This implementation pops from the head of the list. This is unfair,
4766-
// but tends to provide excellent throughput as hot threads remain hot.
4767-
// (We wake recently run threads first).
4768-
//
4769-
// All paths through muxRelease() will execute a CAS.
4770-
// Release consistency -- We depend on the CAS in muxRelease() to provide full
4771-
// bidirectional fence/MEMBAR semantics, ensuring that all prior memory operations
4772-
// executed within the critical section are complete and globally visible before the
4773-
// store (CAS) to the lock-word that releases the lock becomes globally visible.
4774-
void Thread::muxRelease(volatile intptr_t * Lock) {
4775-
for (;;) {
4776-
const intptr_t w = Atomic::cmpxchg(Lock, LOCKBIT, (intptr_t)0);
4777-
assert(w & LOCKBIT, "invariant");
4778-
if (w == LOCKBIT) return;
4779-
ParkEvent * const List = (ParkEvent *) (w & ~LOCKBIT);
4780-
assert(List != NULL, "invariant");
4781-
assert(List->OnList == intptr_t(Lock), "invariant");
4782-
ParkEvent * const nxt = List->ListNext;
4783-
guarantee((intptr_t(nxt) & LOCKBIT) == 0, "invariant");
4784-
4785-
// The following CAS() releases the lock and pops the head element.
4786-
// The CAS() also ratifies the previously fetched lock-word value.
4787-
if (Atomic::cmpxchg(Lock, w, intptr_t(nxt)) != w) {
4788-
continue;
4789-
}
4790-
List->OnList = 0;
4791-
OrderAccess::fence();
4792-
List->unpark();
4793-
return;
4794-
}
4795-
}
4796-
47974641

47984642
void Threads::verify() {
47994643
ALL_JAVA_THREADS(p) {

src/hotspot/share/runtime/thread.hpp

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -827,8 +827,8 @@ class Thread: public ThreadShadow {
827827
public:
828828
volatile intptr_t _Stalled;
829829
volatile int _TypeTag;
830-
ParkEvent * _ParkEvent; // for Object monitors and JVMTI raw monitors
831-
ParkEvent * _MuxEvent; // for low-level muxAcquire-muxRelease
830+
ParkEvent * _ParkEvent; // for Object monitors, JVMTI raw monitors,
831+
// and ObjectSynchronizer::read_stable_mark
832832
int NativeSyncRecursion; // diagnostic
833833

834834
volatile int _OnTrap; // Resume-at IP delta
@@ -837,13 +837,10 @@ class Thread: public ThreadShadow {
837837
jint _hashStateY;
838838
jint _hashStateZ;
839839

840-
// Low-level leaf-lock primitives used to implement synchronization
841-
// and native monitor-mutex infrastructure.
840+
// Low-level leaf-lock primitives used to implement synchronization.
842841
// Not for general synchronization use.
843842
static void SpinAcquire(volatile int * Lock, const char * Name);
844843
static void SpinRelease(volatile int * Lock);
845-
static void muxAcquire(volatile intptr_t * Lock, const char * Name);
846-
static void muxRelease(volatile intptr_t * Lock);
847844
};
848845

849846
// Inline implementation of Thread::current()

0 commit comments

Comments
 (0)