diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 8762d54f9c27512163e1429d629ac0871111bf43..afe1f9abb9b42447b18e74c55b093acd924991f1 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -284,6 +284,7 @@ target_link_libraries(
     ${BOOST_SYSTEM_LIBRARY}
     ${GOOGLE_PERFTOOLS_LIBRARIES}
     ${URIPARSER_LIBRARIES}
+    absl::synchronization
     absl::flat_hash_map
     absl::node_hash_map
     absl::strings
diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h
index 92e0a13683e10b64d89a69d6f429ff20daa838a2..eadb5ec17ae5eff9f910bfd1fbafbdaec2268b98 100644
--- a/indra/llcommon/llinstancetracker.h
+++ b/indra/llcommon/llinstancetracker.h
@@ -34,8 +34,9 @@
 #include <typeinfo>
 #include <memory>
 #include <type_traits>
+#include <mutex>
 
-#include "mutex.h"
+#include "absl/synchronization/mutex.h"
 
 #include <boost/iterator/transform_iterator.hpp>
 #include <boost/iterator/indirect_iterator.hpp>
@@ -52,7 +53,7 @@ namespace LLInstanceTrackerPrivate
     struct StaticBase
     {
         // We need to be able to lock static data while manipulating it.
-        std::mutex mMutex;
+        absl::Mutex mMutex;
     };
 
     void logerrs(const char* cls, const std::string&, const std::string&, const std::string&);
@@ -80,7 +81,7 @@ class LLInstanceTracker
     {
         InstanceMap mMap;
     };
-    typedef llthread::LockStatic<StaticData> LockStatic;
+    typedef llthread::LockStaticAbsl<StaticData> LockStatic;
 
 public:
     // snapshot of std::pair<const KEY, std::shared_ptr<T>> pairs
@@ -307,7 +308,7 @@ class LLInstanceTracker<T, void, KEY_COLLISION_BEHAVIOR>
     {
         InstanceSet mSet;
     };
-    typedef llthread::LockStatic<StaticData> LockStatic;
+    typedef llthread::LockStaticAbsl<StaticData> LockStatic;
 
 public:
     /**
diff --git a/indra/llcommon/lockstatic.h b/indra/llcommon/lockstatic.h
index 96c53c64732b6ab45900b1b53e1abab1e43becff..69c8026a5e101c3df15eaaf402988753660c6020 100644
--- a/indra/llcommon/lockstatic.h
+++ b/indra/llcommon/lockstatic.h
@@ -13,7 +13,9 @@
 #if ! defined(LL_LOCKSTATIC_H)
 #define LL_LOCKSTATIC_H
 
-#include "mutex.h"                  // std::unique_lock
+#include <mutex>
+
+#include "absl/synchronization/mutex.h"
 
 namespace llthread
 {
@@ -68,6 +70,100 @@ class LockStatic
     }
 };
 
+
+// The same as above but for absl::Mutex
+template <typename Static>
+class LockStaticAbsl
+{
+    typedef absl::ReleasableMutexLock lock_t;
+public:
+    LockStaticAbsl() :
+        mData(getStatic()),
+        mLock(&mData->mMutex)
+    {}
+    Static* get() const { return mData; }
+    operator Static* () const { return get(); }
+    Static* operator->() const { return get(); }
+    // sometimes we must explicitly unlock...
+    void unlock()
+    {
+        // but once we do, access is no longer permitted
+        mData = nullptr;
+        mLock.Release();
+    }
+protected:
+    Static* mData;
+    lock_t mLock;
+private:
+    Static* getStatic()
+    {
+        // Static::mMutex must be function-local static rather than class-
+        // static. Some of our consumers must function properly (therefore
+        // lock properly) even when the containing module's static variables
+        // have not yet been runtime-initialized. A mutex requires
+        // construction. A static class member might not yet have been
+        // constructed.
+        //
+        // We could store a dumb mutex_t*, notice when it's NULL and allocate a
+        // heap mutex -- but that's vulnerable to race conditions. And we can't
+        // defend the dumb pointer with another mutex.
+        //
+        // We could store a std::atomic<mutex_t*> -- but a default-constructed
+        // std::atomic<T> does not contain a valid T, even a default-constructed
+        // T! Which means std::atomic, too, requires runtime initialization.
+        //
+        // But a function-local static is guaranteed to be initialized exactly
+        // once: the first time control reaches that declaration.
+        static Static sData;
+        return &sData;
+    }
+};
+
+// Instantiate this template to obtain a pointer to the canonical static
+// instance of Static with no lock. Aka NoOp
+template <typename Static>
+class LockStaticNoOp
+{
+public:
+    LockStaticNoOp():
+        mData(getStatic())
+    {}
+    Static* get() const { return mData; }
+    operator Static*() const { return get(); }
+    Static* operator->() const { return get(); }
+    // sometimes we must explicitly unlock...
+    void unlock()
+    {
+        // but once we do, access is no longer permitted
+        mData = nullptr;
+    }
+protected:
+    Static* mData;
+private:
+    Static* getStatic()
+    {
+        // Static::mMutex must be function-local static rather than class-
+        // static. Some of our consumers must function properly (therefore
+        // lock properly) even when the containing module's static variables
+        // have not yet been runtime-initialized. A mutex requires
+        // construction. A static class member might not yet have been
+        // constructed.
+        //
+        // We could store a dumb mutex_t*, notice when it's NULL and allocate a
+        // heap mutex -- but that's vulnerable to race conditions. And we can't
+        // defend the dumb pointer with another mutex.
+        //
+        // We could store a std::atomic<mutex_t*> -- but a default-constructed
+        // std::atomic<T> does not contain a valid T, even a default-constructed
+        // T! Which means std::atomic, too, requires runtime initialization.
+        //
+        // But a function-local static is guaranteed to be initialized exactly
+        // once: the first time control reaches that declaration.
+        static Static sData;
+        return &sData;
+    }
+};
+
 } // llthread namespace
 
 #endif /* ! defined(LL_LOCKSTATIC_H) */