diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index af41b9e460c75a609d213eae4f808734a9c4f80b..98e1c00ce30dae4013da2cba8b4d7d51cfc1ea2e 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -247,6 +247,7 @@ set(llcommon_HEADER_FILES
     llwin32headers.h
     llwin32headerslean.h
     llworkerthread.h
+    lockstatic.h
     stdtypes.h
     stringize.h
     timer.h
diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h
index 272ad8086ed2393e8bad4b6d0e4494087b6e2d53..cfb40c25f0fbad54e443cff3555fc90497f79ffa 100644
--- a/indra/llcommon/llinstancetracker.h
+++ b/indra/llcommon/llinstancetracker.h
@@ -41,64 +41,20 @@
 #include <boost/iterator/indirect_iterator.hpp>
 #include <boost/iterator/filter_iterator.hpp>
 
+#include "lockstatic.h"
+
 /*****************************************************************************
-*   LLInstanceTrackerBase
+*   StaticBase
 *****************************************************************************/
-/**
- * Base class manages "class-static" data that must actually have singleton
- * semantics: one instance per process, rather than one instance per module as
- * sometimes happens with data simply declared static.
- */
 namespace LLInstanceTrackerStuff
 {
     struct StaticBase
     {
         // We need to be able to lock static data while manipulating it.
-        typedef std::mutex mutex_t;
-        mutex_t mMutex;
+        std::mutex mMutex;
     };
 } // namespace LLInstanceTrackerStuff
 
-template <class Static>
-class LL_COMMON_API LLInstanceTrackerBase
-{
-protected:
-    typedef Static StaticData;
-
-    // Instantiate this class to obtain a pointer to the canonical static
-    // instance of class Static while holding a lock on that instance. Use of
-    // Static::mMutex presumes either that Static is derived from StaticBase,
-    // or that Static declares some other suitable mMutex.
-    class LockStatic
-    {
-        typedef std::unique_lock<decltype(Static::mMutex)> lock_t;
-    public:
-        LockStatic():
-            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.unlock();
-        }
-    protected:
-        Static* mData;
-        lock_t mLock;
-    private:
-        Static* getStatic()
-        {
-            static Static sData;
-            return &sData;
-        }
-    };
-};
-
 /*****************************************************************************
 *   LLInstanceTracker with key
 *****************************************************************************/
@@ -108,29 +64,20 @@ enum EInstanceTrackerAllowKeyCollisions
     LLInstanceTrackerReplaceOnCollision
 };
 
-namespace LLInstanceTrackerStuff
-{
-    template <typename KEY, typename VALUE>
-    struct StaticMap: public StaticBase
-    {
-        typedef std::map<KEY, VALUE> InstanceMap;
-        InstanceMap mMap;
-    };
-} // LLInstanceTrackerStuff
-
 /// This mix-in class adds support for tracking all instances of the specified class parameter T
 /// The (optional) key associates a value of type KEY with a given instance of T, for quick lookup
 /// If KEY is not provided, then instances are stored in a simple set
 /// @NOTE: see explicit specialization below for default KEY==void case
 template<typename T, typename KEY = void,
          EInstanceTrackerAllowKeyCollisions KEY_COLLISION_BEHAVIOR = LLInstanceTrackerErrorOnCollision>
-class LLInstanceTracker :
-    public LLInstanceTrackerBase<LLInstanceTrackerStuff::StaticMap<KEY, std::shared_ptr<T>>>
+class LLInstanceTracker
 {
-    typedef LLInstanceTrackerBase<LLInstanceTrackerStuff::StaticMap<KEY, std::shared_ptr<T>>> super;
-    using typename super::StaticData;
-    using typename super::LockStatic;
-    typedef typename StaticData::InstanceMap InstanceMap;
+    typedef std::map<KEY, std::shared_ptr<T>> InstanceMap;
+    struct StaticData: public LLInstanceTrackerStuff::StaticBase
+    {
+        InstanceMap mMap;
+    };
+    typedef llthread::LockStatic<StaticData> LockStatic;
 
 public:
     // snapshot of std::pair<const KEY, std::shared_ptr<T>> pairs
@@ -334,16 +281,6 @@ class LLInstanceTracker :
 /*****************************************************************************
 *   LLInstanceTracker without key
 *****************************************************************************/
-namespace LLInstanceTrackerStuff
-{
-    template <typename VALUE>
-    struct StaticSet: public StaticBase
-    {
-        typedef std::set<VALUE> InstanceSet;
-        InstanceSet mSet;
-    };
-} // LLInstanceTrackerStuff
-
 // TODO:
 // - For the case of omitted KEY template parameter, consider storing
 //   std::map<T*, std::shared_ptr<T>> instead of std::set<std::shared_ptr<T>>.
@@ -359,13 +296,14 @@ namespace LLInstanceTrackerStuff
 /// explicit specialization for default case where KEY is void
 /// use a simple std::set<T*>
 template<typename T, EInstanceTrackerAllowKeyCollisions KEY_COLLISION_BEHAVIOR>
-class LLInstanceTracker<T, void, KEY_COLLISION_BEHAVIOR> :
-    public LLInstanceTrackerBase<LLInstanceTrackerStuff::StaticSet<std::shared_ptr<T>>>
+class LLInstanceTracker<T, void, KEY_COLLISION_BEHAVIOR>
 {
-    typedef LLInstanceTrackerBase<LLInstanceTrackerStuff::StaticSet<std::shared_ptr<T>>> super;
-    using typename super::StaticData;
-    using typename super::LockStatic;
-    typedef typename StaticData::InstanceSet InstanceSet;
+    typedef std::set<std::shared_ptr<T>> InstanceSet;
+    struct StaticData: public LLInstanceTrackerStuff::StaticBase
+    {
+        InstanceSet mSet;
+    };
+    typedef llthread::LockStatic<StaticData> LockStatic;
 
 public:
     /**
diff --git a/indra/llcommon/lockstatic.h b/indra/llcommon/lockstatic.h
new file mode 100644
index 0000000000000000000000000000000000000000..5f08742cae565c3dfc8403de0f0396a1439dfef9
--- /dev/null
+++ b/indra/llcommon/lockstatic.h
@@ -0,0 +1,56 @@
+/**
+ * @file   lockstatic.h
+ * @author Nat Goodspeed
+ * @date   2019-12-03
+ * @brief  LockStatic class provides mutex-guarded access to the specified
+ *         static data.
+ * 
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LOCKSTATIC_H)
+#define LL_LOCKSTATIC_H
+
+#include "mutex.h"                  // std::unique_lock
+
+namespace llthread
+{
+
+// Instantiate this template to obtain a pointer to the canonical static
+// instance of Static while holding a lock on that instance. Use of
+// Static::mMutex presumes that Static declares some suitable mMutex.
+template <typename Static>
+class LockStatic
+{
+    typedef std::unique_lock<decltype(Static::mMutex)> lock_t;
+public:
+    LockStatic():
+        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.unlock();
+    }
+protected:
+    Static* mData;
+    lock_t mLock;
+private:
+    Static* getStatic()
+    {
+        static Static sData;
+        return &sData;
+    }
+};
+
+} // llthread namespace
+
+#endif /* ! defined(LL_LOCKSTATIC_H) */