You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by ds...@apache.org on 2018/10/05 19:24:53 UTC

[geode] branch develop updated: GEODE-5780: remove LoggingThreadGroup

This is an automated email from the ASF dual-hosted git repository.

dschneider pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode.git


The following commit(s) were added to refs/heads/develop by this push:
     new 5289607  GEODE-5780: remove LoggingThreadGroup
5289607 is described below

commit 528960760624672fcbbf36c7e73b93cb224597f9
Author: Darrel Schneider <ds...@pivotal.io>
AuthorDate: Fri Oct 5 12:22:52 2018 -0700

    GEODE-5780: remove LoggingThreadGroup
    
    GEODE-5783: have a single source for ThreadFactory
    
    LoggingThreadGroup is no longer used and has been removed.
    
    LoggingUnhandledExceptionHandler creates a single handler that logs
    to a static logger. All geode threads should use this singleton.
    
    LoggingThread should be used when creating a Thread.
    It will automatically set a LoggingUnhandledExceptionHandler on the thread.
    
    LoggingThreadFactory makes it easy to create a ThreadFactory instance that
    uses LoggingThread and gives each thread a unique name.
    
    LoggingExecutors should be used in place of Executors.
    Each executor it creates will have a LoggingThreadFactory.
    
    All the places geode used Thread, ThreadGroup, ThreadFactory, and Executors have
    been changed to use these classes. The only exception is some of the add on modules
    (like redis, and memcached) were not changed so that they would not use new internal
    geode APIs.
    This checkin uses these new classes in every place the previously used
    LoggingThreadGroup. The code should also be reviewed for other places
    we create Thread or ThreadFactory instances without using the LoggingThreadGroup.
---
 .../internal/cache/eviction/EvictionTestBase.java  |   3 +-
 .../AutoConnectionSourceImplJUnitTest.java         |   4 +-
 .../AsyncIndexUpdaterThreadShutdownJUnitTest.java  |  46 +-
 .../tcpserver/TCPClientSSLIntegrationTest.java     |   7 +-
 .../internal/tcpserver/TCPServerSSLJUnitTest.java  |   7 +-
 .../internal/tcpserver/TcpServerJUnitTest.java     |   2 +-
 .../apache/geode/codeAnalysis/excludedClasses.txt  |   2 +-
 .../main/java/org/apache/geode/SystemFailure.java  |  44 +-
 .../internal/DistributedSystemHealthMonitor.java   |   8 +-
 .../internal/EnabledManagedEntityController.java   |  23 +-
 .../apache/geode/admin/jmx/internal/AgentImpl.java |  13 +-
 .../geode/admin/jmx/internal/AgentLauncher.java    |  26 +-
 .../geode/cache/client/internal/PoolImpl.java      |  20 +-
 .../cache/client/internal/QueueManagerImpl.java    |  17 +-
 .../client/internal/SingleHopClientExecutor.java   |  18 +-
 .../internal/pooling/ConnectionManagerImpl.java    |  15 +-
 .../cache/query/internal/index/IndexManager.java   |  13 +-
 .../apache/geode/distributed/ServerLauncher.java   |  21 +-
 .../internal/ClusterDistributionManager.java       | 536 +++++++--------------
 .../distributed/internal/HealthMonitorImpl.java    |   9 +-
 .../internal/InternalDistributedSystem.java        |  84 ++--
 .../distributed/internal/InternalLocator.java      |  43 +-
 .../distributed/internal/LocatorLoadSnapshot.java  |  16 +-
 .../internal/LonerDistributionManager.java         |   5 +-
 .../distributed/internal/locks/DLockGrantor.java   |   9 +-
 .../distributed/internal/locks/DLockService.java   |  21 -
 .../internal/membership/gms/Services.java          |  11 -
 .../membership/gms/fd/GMSHealthMonitor.java        |  39 +-
 .../membership/gms/membership/GMSJoinLeave.java    |  27 +-
 .../membership/gms/mgr/GMSMembershipManager.java   |  78 ++-
 .../distributed/internal/tcpserver/TcpServer.java  |  46 +-
 .../apache/geode/internal/DistributionLocator.java |  18 +-
 .../java/org/apache/geode/internal/OSProcess.java  |  85 ++--
 .../internal/admin/remote/RemoteGemFireVM.java     |   6 +-
 .../admin/remote/RemoteGfManagerAgent.java         |  31 +-
 .../internal/admin/remote/ShutdownAllRequest.java  |  20 +-
 .../geode/internal/cache/ColocationLogger.java     |   3 +-
 .../apache/geode/internal/cache/DiskStoreImpl.java |  75 +--
 .../geode/internal/cache/DiskStoreMonitor.java     |  29 +-
 .../geode/internal/cache/DistributedRegion.java    |  48 +-
 .../apache/geode/internal/cache/ExpiryTask.java    |  45 +-
 .../geode/internal/cache/GemFireCacheImpl.java     |  54 +--
 .../geode/internal/cache/GemfireCacheHelper.java   |  36 --
 .../internal/cache/InitialImageOperation.java      |  14 +-
 .../internal/cache/PRHARedundancyProvider.java     |   8 +-
 .../geode/internal/cache/PRQueryProcessor.java     |   4 +-
 .../geode/internal/cache/PartitionedRegion.java    |  14 +-
 .../geode/internal/cache/TXCommitMessage.java      |  43 +-
 .../geode/internal/cache/TombstoneService.java     |   7 +-
 .../geode/internal/cache/backup/BackupService.java |  20 +-
 .../internal/cache/control/HeapMemoryMonitor.java  |  16 +-
 .../cache/control/InternalResourceManager.java     |  47 +-
 .../cache/control/OffHeapMemoryMonitor.java        |   9 +-
 .../geode/internal/cache/eviction/HeapEvictor.java |  45 +-
 .../cache/eviction/LRUListWithAsyncSorting.java    |  18 +-
 .../internal/cache/eviction/OffHeapEvictor.java    |   4 +-
 .../geode/internal/cache/ha/HARegionQueue.java     |  39 +-
 .../cache/tier/InternalClientMembership.java       |  26 +-
 .../internal/cache/tier/sockets/AcceptorImpl.java  | 156 ++----
 .../cache/tier/sockets/CacheClientProxy.java       |   8 +-
 .../cache/tier/sockets/CacheClientUpdater.java     |   8 +-
 .../cache/tier/sockets/ClientHealthMonitor.java    |   8 +-
 .../cache/tier/sockets/TcpServerFactory.java       |   4 +-
 .../tier/sockets/command/ExecuteFunction66.java    |  15 +-
 .../internal/cache/wan/AbstractGatewaySender.java  |   6 +-
 .../wan/AbstractGatewaySenderEventProcessor.java   |   8 +-
 .../internal/cache/wan/GatewaySenderAdvisor.java   |  71 ++-
 ...currentParallelGatewaySenderEventProcessor.java |  25 +-
 .../ParallelGatewaySenderEventProcessor.java       |  12 +-
 .../wan/parallel/ParallelGatewaySenderQueue.java   |  18 +-
 ...oncurrentSerialGatewaySenderEventProcessor.java |  25 +-
 .../serial/SerialGatewaySenderEventProcessor.java  |  26 +-
 .../internal/datasource/AbstractPoolCache.java     |   6 +-
 .../geode/internal/jta/TransactionManagerImpl.java |   9 +-
 .../geode/internal/logging/LogWriterImpl.java      |  55 ++-
 .../geode/internal/logging/LoggingExecutors.java   | 306 ++++++++++++
 .../geode/internal/logging/LoggingThread.java      |  58 +++
 .../internal/logging/LoggingThreadFactory.java     | 131 +++++
 .../geode/internal/logging/LoggingThreadGroup.java | 328 -------------
 .../logging/LoggingUncaughtExceptionHandler.java   | 107 ++++
 .../memcached/commands/StorageCommand.java         |  12 +-
 .../apache/geode/internal/net/SocketCloser.java    |  28 +-
 .../DisconnectingOutOfOffHeapMemoryListener.java   |   7 +-
 .../internal/process/ControlFileWatchdog.java      |   4 +-
 .../internal/process/ProcessStreamReader.java      |   8 +-
 .../geode/internal/statistics/CallbackSampler.java |  12 +-
 .../geode/internal/statistics/HostStatSampler.java |  11 +-
 .../internal/statistics/StatMonitorHandler.java    |   4 +-
 .../org/apache/geode/internal/tcp/Connection.java  |  11 +-
 .../apache/geode/internal/tcp/ConnectionTable.java |  32 +-
 .../org/apache/geode/internal/tcp/TCPConduit.java  |  59 +--
 .../concurrent/CustomEntryConcurrentHashMap.java   |   4 +-
 .../management/internal/FederatingManager.java     |   5 +-
 .../geode/management/internal/LocalManager.java    |  35 +-
 .../internal/beans/MemberMBeanBridge.java          |  22 +-
 .../internal/cli/commands/RebalanceCommand.java    |   5 +-
 .../internal/cli/commands/ShutdownCommand.java     |   4 +-
 .../cli/commands/StartGatewaySenderCommand.java    |  15 +-
 .../internal/cli/functions/ShutDownFunction.java   |   4 +-
 .../geode/management/internal/cli/shell/Gfsh.java  |   3 +-
 .../java/org/apache/geode/cache30/TestHeapLRU.java |  36 +-
 .../cache/tier/sockets/TcpServerFactoryTest.java   |   2 +-
 .../internal/logging/LoggingThreadFactoryTest.java | 116 +++++
 .../geode/internal/logging/LoggingThreadTest.java  |  73 +++
 .../LoggingUncaughtExceptionHandlerTest.java       | 117 +++++
 .../cache/query/internal/cq/ClientCQImpl.java      |  49 +-
 .../locator/wan/WanLocatorDiscovererImpl.java      |  17 +-
 107 files changed, 1814 insertions(+), 2248 deletions(-)

diff --git a/geode-core/src/distributedTest/java/org/apache/geode/internal/cache/eviction/EvictionTestBase.java b/geode-core/src/distributedTest/java/org/apache/geode/internal/cache/eviction/EvictionTestBase.java
index 487920b..666c0da 100755
--- a/geode-core/src/distributedTest/java/org/apache/geode/internal/cache/eviction/EvictionTestBase.java
+++ b/geode-core/src/distributedTest/java/org/apache/geode/internal/cache/eviction/EvictionTestBase.java
@@ -25,6 +25,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
+import java.util.concurrent.ThreadPoolExecutor;
 
 import org.apache.geode.cache.AttributesFactory;
 import org.apache.geode.cache.Cache;
@@ -408,7 +409,7 @@ public class EvictionTestBase extends JUnit4CacheTestCase {
         new SerializableCallable("Validate evictions") {
           public Object call() throws Exception {
             return getEvictor().getEvictorThreadPool() != null
-                ? getEvictor().getEvictorThreadPool().getTaskCount() : 0;
+                ? ((ThreadPoolExecutor) getEvictor().getEvictorThreadPool()).getTaskCount() : 0;
           }
         };
     Long taskCountOfVM = (Long) dataStore1.invoke(getThreadPoolTaskCount);
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/cache/client/internal/AutoConnectionSourceImplJUnitTest.java b/geode-core/src/integrationTest/java/org/apache/geode/cache/client/internal/AutoConnectionSourceImplJUnitTest.java
index 3758049..64bde15 100644
--- a/geode-core/src/integrationTest/java/org/apache/geode/cache/client/internal/AutoConnectionSourceImplJUnitTest.java
+++ b/geode-core/src/integrationTest/java/org/apache/geode/cache/client/internal/AutoConnectionSourceImplJUnitTest.java
@@ -328,7 +328,7 @@ public class AutoConnectionSourceImplJUnitTest {
     int secondPort = AvailablePortHelper.getRandomAvailableTCPPort();
     TcpServer server2 =
         new TcpServerFactory().makeTcpServer(secondPort, InetAddress.getLocalHost(), null, null,
-            handler, new FakeHelper(), Thread.currentThread().getThreadGroup(), "tcp server", null);
+            handler, new FakeHelper(), "tcp server", null);
     server2.start();
 
     try {
@@ -412,7 +412,7 @@ public class AutoConnectionSourceImplJUnitTest {
 
   private void startFakeLocator() throws UnknownHostException, IOException, InterruptedException {
     server = new TcpServerFactory().makeTcpServer(port, InetAddress.getLocalHost(), null, null,
-        handler, new FakeHelper(), Thread.currentThread().getThreadGroup(), "Tcp Server", null);
+        handler, new FakeHelper(), "Tcp Server", null);
     server.start();
     Thread.sleep(500);
   }
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/cache/query/internal/index/AsyncIndexUpdaterThreadShutdownJUnitTest.java b/geode-core/src/integrationTest/java/org/apache/geode/cache/query/internal/index/AsyncIndexUpdaterThreadShutdownJUnitTest.java
index 394633b..87860e9 100644
--- a/geode-core/src/integrationTest/java/org/apache/geode/cache/query/internal/index/AsyncIndexUpdaterThreadShutdownJUnitTest.java
+++ b/geode-core/src/integrationTest/java/org/apache/geode/cache/query/internal/index/AsyncIndexUpdaterThreadShutdownJUnitTest.java
@@ -15,10 +15,13 @@
 package org.apache.geode.cache.query.internal.index;
 
 import static org.apache.geode.distributed.ConfigurationProperties.MCAST_PORT;
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.util.HashSet;
+
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 
@@ -29,8 +32,9 @@ import org.apache.geode.cache.RegionFactory;
 import org.apache.geode.cache.RegionShortcut;
 import org.apache.geode.cache.query.data.Portfolio;
 import org.apache.geode.cache.query.internal.index.IndexManager.IndexUpdaterThread;
-import org.apache.geode.internal.cache.GemFireCacheImpl;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.cache.BucketRegion;
+import org.apache.geode.internal.cache.InternalRegion;
+import org.apache.geode.internal.cache.PartitionedRegion;
 import org.apache.geode.test.junit.categories.OQLIndexTest;
 
 /**
@@ -68,18 +72,14 @@ public class AsyncIndexUpdaterThreadShutdownJUnitTest {
       localRegion.put(i, new Portfolio(i));
     }
 
-    GemFireCacheImpl internalCache = (GemFireCacheImpl) cache;
-    // Don't disconnect distributed system yet to keep system thread groups alive.
-    internalCache.close("Normal disconnect", null, false, false);
-
-    // Get Asynchronous index updater thread group from Distributed System.
-    ThreadGroup indexUpdaterThreadGroup =
-        LoggingThreadGroup.getThreadGroup("QueryMonitor Thread Group");
+    InternalRegion internalRegion = (InternalRegion) localRegion;
+    assertTrue(internalRegion.getIndexManager().getUpdaterThread().isAlive());
 
-    assertEquals(0, indexUpdaterThreadGroup.activeCount());
+    localRegion.close();
 
-    internalCache.getSystem().disconnect();
+    assertFalse(internalRegion.getIndexManager().getUpdaterThread().isAlive());
 
+    cache.close();
   }
 
   @Test
@@ -104,16 +104,22 @@ public class AsyncIndexUpdaterThreadShutdownJUnitTest {
       localRegion.put(i, new Portfolio(i));
     }
 
-    GemFireCacheImpl internalCache = (GemFireCacheImpl) cache;
-    // Don't disconnect distributed system yet to keep system thread groups alive.
-    internalCache.close("Normal disconnect", null, false, false);
+    InternalRegion internalRegion = (InternalRegion) localRegion;
+    assertTrue(internalRegion.getIndexManager().getUpdaterThread().isAlive());
 
-    // Get Asynchronous index updater thread group from Distributed System.
-    ThreadGroup indexUpdaterThreadGroup =
-        LoggingThreadGroup.getThreadGroup("QueryMonitor Thread Group");
+    PartitionedRegion pr = (PartitionedRegion) localRegion;
+    HashSet<BucketRegion> buckets = new HashSet<>(pr.getDataStore().getAllLocalBucketRegions());
+    for (BucketRegion br : buckets) {
+      assertTrue(br.getIndexManager().getUpdaterThread().isAlive());
+    }
 
-    assertEquals(0, indexUpdaterThreadGroup.activeCount());
+    localRegion.close();
+
+    assertFalse(internalRegion.getIndexManager().getUpdaterThread().isAlive());
+    for (BucketRegion br : buckets) {
+      assertFalse(br.getIndexManager().getUpdaterThread().isAlive());
+    }
 
-    internalCache.getSystem().disconnect();
+    cache.close();
   }
 }
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/distributed/internal/tcpserver/TCPClientSSLIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/distributed/internal/tcpserver/TCPClientSSLIntegrationTest.java
index dc55a23..f327db2 100644
--- a/geode-core/src/integrationTest/java/org/apache/geode/distributed/internal/tcpserver/TCPClientSSLIntegrationTest.java
+++ b/geode-core/src/integrationTest/java/org/apache/geode/distributed/internal/tcpserver/TCPClientSSLIntegrationTest.java
@@ -102,7 +102,7 @@ public class TCPClientSSLIntegrationTest {
 
     server = new FakeTcpServer(port, localhost, sslProperties, null,
         tcpHandler, Mockito.mock(PoolStatHelper.class),
-        Thread.currentThread().getThreadGroup(), "server thread");
+        "server thread");
     server.start();
   }
 
@@ -176,9 +176,8 @@ public class TCPClientSSLIntegrationTest {
 
     public FakeTcpServer(int port, InetAddress bind_address, Properties sslConfig,
         DistributionConfigImpl cfg, TcpHandler handler, PoolStatHelper poolHelper,
-        ThreadGroup threadGroup, String threadName) {
-      super(port, bind_address, sslConfig, cfg, handler, poolHelper, threadGroup, threadName, null,
-          null);
+        String threadName) {
+      super(port, bind_address, sslConfig, cfg, handler, poolHelper, threadName, null, null);
       if (cfg == null) {
         cfg = new DistributionConfigImpl(sslConfig);
       }
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/distributed/internal/tcpserver/TCPServerSSLJUnitTest.java b/geode-core/src/integrationTest/java/org/apache/geode/distributed/internal/tcpserver/TCPServerSSLJUnitTest.java
index 06e30da..ad0248e 100644
--- a/geode-core/src/integrationTest/java/org/apache/geode/distributed/internal/tcpserver/TCPServerSSLJUnitTest.java
+++ b/geode-core/src/integrationTest/java/org/apache/geode/distributed/internal/tcpserver/TCPServerSSLJUnitTest.java
@@ -78,7 +78,7 @@ public class TCPServerSSLJUnitTest {
 
     server = new DummyTcpServer(port, localhost, sslProperties, null,
         Mockito.mock(TcpHandler.class), Mockito.mock(PoolStatHelper.class),
-        Thread.currentThread().getThreadGroup(), "server thread");
+        "server thread");
     server.start();
   }
 
@@ -137,9 +137,8 @@ public class TCPServerSSLJUnitTest {
 
     public DummyTcpServer(int port, InetAddress bind_address, Properties sslConfig,
         DistributionConfigImpl cfg, TcpHandler handler, PoolStatHelper poolHelper,
-        ThreadGroup threadGroup, String threadName) {
-      super(port, bind_address, sslConfig, cfg, handler, poolHelper, threadGroup, threadName, null,
-          null);
+        String threadName) {
+      super(port, bind_address, sslConfig, cfg, handler, poolHelper, threadName, null, null);
       if (cfg == null) {
         cfg = new DistributionConfigImpl(sslConfig);
       }
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/distributed/internal/tcpserver/TcpServerJUnitTest.java b/geode-core/src/integrationTest/java/org/apache/geode/distributed/internal/tcpserver/TcpServerJUnitTest.java
index 4f83560..070a770 100644
--- a/geode-core/src/integrationTest/java/org/apache/geode/distributed/internal/tcpserver/TcpServerJUnitTest.java
+++ b/geode-core/src/integrationTest/java/org/apache/geode/distributed/internal/tcpserver/TcpServerJUnitTest.java
@@ -78,7 +78,7 @@ public class TcpServerJUnitTest {
 
     stats = new SimpleStats();
     server = new TcpServerFactory().makeTcpServer(port, localhost, new Properties(), null, handler,
-        stats, Thread.currentThread().getThreadGroup(), "server thread", null);
+        stats, "server thread", null);
     server.start();
   }
 
diff --git a/geode-core/src/integrationTest/resources/org/apache/geode/codeAnalysis/excludedClasses.txt b/geode-core/src/integrationTest/resources/org/apache/geode/codeAnalysis/excludedClasses.txt
index d9df46b..6bcbae3 100644
--- a/geode-core/src/integrationTest/resources/org/apache/geode/codeAnalysis/excludedClasses.txt
+++ b/geode-core/src/integrationTest/resources/org/apache/geode/codeAnalysis/excludedClasses.txt
@@ -74,7 +74,7 @@ org/apache/geode/security/ResourcePermission
 org/apache/geode/security/ResourcePermission$Operation
 org/apache/geode/security/ResourcePermission$Resource
 org/apache/geode/security/ResourcePermission$Target
-org/apache/geode/internal/cache/PartitionedRegion$7
+org/apache/geode/internal/cache/PartitionedRegion$6
 org/apache/geode/internal/cache/TXFarSideCMTracker$2
 org/apache/geode/internal/cache/TXManagerImpl$1
 org/apache/geode/internal/cache/partitioned/FetchEntriesMessage$FetchEntriesResponse$1
diff --git a/geode-core/src/main/java/org/apache/geode/SystemFailure.java b/geode-core/src/main/java/org/apache/geode/SystemFailure.java
index 307114e..91d045f 100644
--- a/geode-core/src/main/java/org/apache/geode/SystemFailure.java
+++ b/geode-core/src/main/java/org/apache/geode/SystemFailure.java
@@ -20,6 +20,7 @@ import org.apache.geode.internal.SystemFailureTestHook;
 import org.apache.geode.internal.admin.remote.RemoteGfManagerAgent;
 import org.apache.geode.internal.cache.GemFireCacheImpl;
 import org.apache.geode.internal.i18n.LocalizedStrings;
+import org.apache.geode.internal.logging.LoggingThread;
 
 /**
  * Catches and responds to JVM failure
@@ -120,29 +121,6 @@ import org.apache.geode.internal.i18n.LocalizedStrings;
           ...
  * </pre>
  *
- * <h2>Create Logging ThreadGroups</h2> If you create any Thread, a best practice is to catch severe
- * errors and signal failure appropriately. One trick to do this is to create a ThreadGroup that
- * handles uncaught exceptions by overriding
- * {@link ThreadGroup#uncaughtException(Thread, Throwable)} and to declare your thread as a member
- * of that {@link ThreadGroup}. This also has a significant side-benefit in that most uncaught
- * exceptions can be detected:
- * <p>
- *
- * <pre>
-    ThreadGroup tg = new ThreadGroup("Worker Threads") {
-        public void uncaughtException(Thread t, Throwable e) {
-          // Do this *before* any object allocation in case of
-          // OutOfMemoryError (for instance)
-          if (e instanceof VirtualMachineError) {
-            SystemFailure.{@link #setFailure(Error) setFailure}((VirtualMachineError)e); // don't throw
-          }
-          String s = "Uncaught exception in thread " + t;
-          system.getLogWriter().severe(s, e);
-        }
-        Thread t = new Thread(myRunnable, tg, "My Thread");
-        t.start();
-      }; *
- * </pre>
  * <p>
  * <h2>Catches of Error and Throwable Should Check for Failure</h2> Keep in mind that peculiar or
  * flat-out<em>impossible</em> exceptions may ensue after a VirtualMachineError has been thrown
@@ -276,20 +254,6 @@ public final class SystemFailure {
   private static volatile boolean failureActionCompleted = false;
 
   /**
-   * This is a logging ThreadGroup, created only once.
-   */
-  private static final ThreadGroup tg;
-  static {
-    tg = new ThreadGroup("SystemFailure Watchdog Threads") {
-      @Override
-      public void uncaughtException(Thread t, Throwable e) {
-        System.err.println("Internal error in SystemFailure watchdog:" + e);
-        e.printStackTrace();
-      }
-    };
-  }
-
-  /**
    * This is the amount of time, in seconds, the watchdog periodically awakens to see if the system
    * has been corrupted.
    * <p>
@@ -343,8 +307,7 @@ public final class SystemFailure {
       if (watchDog != null && watchDog.isAlive()) {
         return;
       }
-      watchDog = new Thread(tg, SystemFailure::runWatchDog, "SystemFailure WatchDog");
-      watchDog.setDaemon(true);
+      watchDog = new LoggingThread("SystemFailure WatchDog", SystemFailure::runWatchDog);
       watchDog.start();
     }
   }
@@ -568,8 +531,7 @@ public final class SystemFailure {
       if (proctor != null && proctor.isAlive()) {
         return;
       }
-      proctor = new Thread(tg, SystemFailure::runProctor, "SystemFailure Proctor");
-      proctor.setDaemon(true);
+      proctor = new LoggingThread("SystemFailure Proctor", SystemFailure::runProctor);
       proctor.start();
     }
   }
diff --git a/geode-core/src/main/java/org/apache/geode/admin/internal/DistributedSystemHealthMonitor.java b/geode-core/src/main/java/org/apache/geode/admin/internal/DistributedSystemHealthMonitor.java
index a429433..bd888b2 100644
--- a/geode-core/src/main/java/org/apache/geode/admin/internal/DistributedSystemHealthMonitor.java
+++ b/geode-core/src/main/java/org/apache/geode/admin/internal/DistributedSystemHealthMonitor.java
@@ -44,7 +44,7 @@ import org.apache.geode.internal.admin.StatListener;
 import org.apache.geode.internal.admin.StatResource;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.net.SocketCreator;
 
@@ -107,13 +107,9 @@ class DistributedSystemHealthMonitor implements Runnable, GemFireVM {
     this.okayDiagnoses = new ArrayList();
     this.poorDiagnoses = new ArrayList();
 
-    ThreadGroup group = LoggingThreadGroup.createThreadGroup(
-        LocalizedStrings.DistributedSystemHealthMonitor_HEALTH_MONITORS.toLocalizedString(),
-        logger);
     String name = LocalizedStrings.DistributedSystemHealthMonitor_HEALTH_MONITOR_FOR_0
         .toLocalizedString(eval.getDescription());
-    this.thread = new Thread(group, this, name);
-    this.thread.setDaemon(true);
+    this.thread = new LoggingThread(name, this);
   }
 
   /**
diff --git a/geode-core/src/main/java/org/apache/geode/admin/internal/EnabledManagedEntityController.java b/geode-core/src/main/java/org/apache/geode/admin/internal/EnabledManagedEntityController.java
index 27b809b..9a19b10 100755
--- a/geode-core/src/main/java/org/apache/geode/admin/internal/EnabledManagedEntityController.java
+++ b/geode-core/src/main/java/org/apache/geode/admin/internal/EnabledManagedEntityController.java
@@ -34,7 +34,7 @@ import org.apache.geode.distributed.internal.DistributionConfig;
 import org.apache.geode.internal.ProcessOutputReader;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 
 /**
@@ -65,11 +65,6 @@ class EnabledManagedEntityController implements ManagedEntityController {
 
   ////////////////////// Instance Fields //////////////////////
 
-  /**
-   * The thread group in which threads launched by this system controller reside.
-   */
-  private final ThreadGroup threadGroup;
-
   /** System to which the managed entities belong */
   private final AdminDistributedSystem system;
 
@@ -81,8 +76,6 @@ class EnabledManagedEntityController implements ManagedEntityController {
    */
   EnabledManagedEntityController(AdminDistributedSystem system) {
     this.system = system;
-    this.threadGroup =
-        LoggingThreadGroup.createThreadGroup("ManagedEntityController threads", logger);
   }
 
   ///////////////////// Instance Methods /////////////////////
@@ -313,11 +306,8 @@ class EnabledManagedEntityController implements ManagedEntityController {
    */
   public void start(final InternalManagedEntity entity) {
     final String command = arrangeRemoteCommand(entity, entity.getStartCommand());
-    Thread start = new Thread(this.threadGroup, new Runnable() {
-      public void run() {
-        execute(command, entity);
-      }
-    }, "Start " + entity.getEntityType());
+    Thread start = new LoggingThread("Start " + entity.getEntityType(),
+        false, () -> execute(command, entity));
     start.start();
   }
 
@@ -326,11 +316,8 @@ class EnabledManagedEntityController implements ManagedEntityController {
    */
   public void stop(final InternalManagedEntity entity) {
     final String command = arrangeRemoteCommand(entity, entity.getStopCommand());
-    Thread stop = new Thread(this.threadGroup, new Runnable() {
-      public void run() {
-        execute(command, entity);
-      }
-    }, "Stop " + entity.getEntityType());
+    Thread stop = new LoggingThread("Stop " + entity.getEntityType(),
+        false, () -> execute(command, entity));
     stop.start();
   }
 
diff --git a/geode-core/src/main/java/org/apache/geode/admin/jmx/internal/AgentImpl.java b/geode-core/src/main/java/org/apache/geode/admin/jmx/internal/AgentImpl.java
index 8b71fca..c7a2092 100644
--- a/geode-core/src/main/java/org/apache/geode/admin/jmx/internal/AgentImpl.java
+++ b/geode-core/src/main/java/org/apache/geode/admin/jmx/internal/AgentImpl.java
@@ -65,7 +65,7 @@ import org.apache.geode.internal.logging.InternalLogWriter;
 import org.apache.geode.internal.logging.LogConfig;
 import org.apache.geode.internal.logging.LogService;
 import org.apache.geode.internal.logging.LogWriterFactory;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.AlertAppender;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.logging.log4j.LogMarker;
@@ -185,6 +185,7 @@ public class AgentImpl implements org.apache.geode.admin.jmx.Agent,
    * @throws IllegalArgumentException if agentConfig is null
    */
   public AgentImpl(AgentConfigImpl agentConfig) throws AdminException, IllegalArgumentException {
+    shutdownHook = new LoggingThread("Shutdown", false, () -> disconnectFromSystem());
     addShutdownHook();
     if (agentConfig == null) {
       throw new IllegalArgumentException(
@@ -362,8 +363,6 @@ public class AgentImpl implements org.apache.geode.admin.jmx.Agent,
       logger.info(LocalizedMessage.create(LocalizedStrings.AgentImpl_AGENT_HAS_STOPPED));
     } finally {
       LogWriterAppenders.destroy(LogWriterAppenders.Identifier.MAIN);
-      LoggingThreadGroup.cleanUpThreadGroups(); // bug35388 - logwriters accumulate, causing mem
-                                                // leak
     }
 
   }
@@ -834,13 +833,7 @@ public class AgentImpl implements org.apache.geode.admin.jmx.Agent,
     return this.logWriter;
   }
 
-  private final Thread shutdownHook =
-      new Thread(LoggingThreadGroup.createThreadGroup("Shutdown"), "Shutdown") {
-        @Override
-        public void run() {
-          disconnectFromSystem();
-        }
-      };
+  private final Thread shutdownHook;
 
   /**
    * Adds a ShutdownHook to the Agent for cleaning up any resources
diff --git a/geode-core/src/main/java/org/apache/geode/admin/jmx/internal/AgentLauncher.java b/geode-core/src/main/java/org/apache/geode/admin/jmx/internal/AgentLauncher.java
index 922dc85..7301462 100644
--- a/geode-core/src/main/java/org/apache/geode/admin/jmx/internal/AgentLauncher.java
+++ b/geode-core/src/main/java/org/apache/geode/admin/jmx/internal/AgentLauncher.java
@@ -23,6 +23,7 @@ import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.PrintStream;
 import java.io.Serializable;
+import java.lang.Thread.UncaughtExceptionHandler;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -49,6 +50,7 @@ import org.apache.geode.internal.OSProcess;
 import org.apache.geode.internal.PureJavaMode;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.net.SocketCreator;
 import org.apache.geode.internal.util.IOUtils;
 import org.apache.geode.internal.util.JavaCommandBuilder;
@@ -367,8 +369,7 @@ public class AgentLauncher {
 
     final Agent agent = createAgent((Properties) options.get(AGENT_PROPS));
 
-    final Thread thread = createAgentProcessThread(createAgentProcessThreadGroup(), agent);
-    thread.setDaemon(true);
+    final Thread thread = createAgentProcessThread(agent);
     thread.start();
 
     // periodically check and see if the JMX Agent has been told to stop
@@ -393,21 +394,20 @@ public class AgentLauncher {
     return AgentFactory.getAgent(config);
   }
 
-  private ThreadGroup createAgentProcessThreadGroup() {
-    return new ThreadGroup(LocalizedStrings.AgentLauncher_STARTING_AGENT.toLocalizedString()) {
-      @Override
-      public void uncaughtException(final Thread t, final Throwable e) {
-        if (e instanceof VirtualMachineError) {
-          SystemFailure.setFailure((VirtualMachineError) e);
-        }
-        setServerError(LocalizedStrings.AgentLauncher_UNCAUGHT_EXCEPTION_IN_THREAD_0
-            .toLocalizedString(t.getName()), e);
+  private UncaughtExceptionHandler createUncaughtExceptionHandler() {
+    return (t, e) -> {
+      if (e instanceof VirtualMachineError) {
+        SystemFailure.setFailure((VirtualMachineError) e);
       }
+      setServerError(LocalizedStrings.AgentLauncher_UNCAUGHT_EXCEPTION_IN_THREAD_0
+          .toLocalizedString(t.getName()), e);
     };
   }
 
-  private Thread createAgentProcessThread(final ThreadGroup group, final Agent agent) {
-    return new Thread(group, createAgentProcessRunnable(agent), "Start agent");
+  private Thread createAgentProcessThread(final Agent agent) {
+    Thread thread = new LoggingThread("Start agent", createAgentProcessRunnable(agent));
+    thread.setUncaughtExceptionHandler(createUncaughtExceptionHandler());
+    return thread;
   }
 
   private Runnable createAgentProcessRunnable(final Agent agent) {
diff --git a/geode-core/src/main/java/org/apache/geode/cache/client/internal/PoolImpl.java b/geode-core/src/main/java/org/apache/geode/cache/client/internal/PoolImpl.java
index 2ed8ec9..d91fd9a 100644
--- a/geode-core/src/main/java/org/apache/geode/cache/client/internal/PoolImpl.java
+++ b/geode-core/src/main/java/org/apache/geode/cache/client/internal/PoolImpl.java
@@ -27,7 +27,6 @@ import java.util.Properties;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -54,7 +53,6 @@ import org.apache.geode.distributed.internal.DistributionConfig;
 import org.apache.geode.distributed.internal.InternalDistributedSystem;
 import org.apache.geode.distributed.internal.ServerLocation;
 import org.apache.geode.distributed.internal.membership.gms.membership.HostAddress;
-import org.apache.geode.internal.ScheduledThreadPoolExecutorWithKeepAlive;
 import org.apache.geode.internal.admin.ClientStatsManager;
 import org.apache.geode.internal.cache.EventID;
 import org.apache.geode.internal.cache.InternalCache;
@@ -65,6 +63,7 @@ import org.apache.geode.internal.cache.tier.sockets.ClientProxyMembershipID;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.InternalLogWriter;
 import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.monitoring.ThreadsMonitoring;
 import org.apache.geode.internal.statistics.DummyStatisticsFactory;
@@ -320,21 +319,8 @@ public class PoolImpl implements InternalPool {
     }
 
     final String timerName = "poolTimer-" + getName() + "-";
-    backgroundProcessor = new ScheduledThreadPoolExecutorWithKeepAlive(BACKGROUND_TASK_POOL_SIZE,
-        BACKGROUND_TASK_POOL_KEEP_ALIVE, TimeUnit.MILLISECONDS, new ThreadFactory() {
-          AtomicInteger threadNum = new AtomicInteger();
-
-          public Thread newThread(final Runnable r) {
-            Thread result = new Thread(r, timerName + threadNum.incrementAndGet());
-            result.setDaemon(true);
-            return result;
-          }
-        }, this.threadMonitoring);
-    ((ScheduledThreadPoolExecutorWithKeepAlive) backgroundProcessor)
-        .setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
-    ((ScheduledThreadPoolExecutorWithKeepAlive) backgroundProcessor)
-        .setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
-
+    backgroundProcessor = LoggingExecutors.newScheduledThreadPool(timerName,
+        BACKGROUND_TASK_POOL_SIZE, BACKGROUND_TASK_POOL_KEEP_ALIVE, threadMonitoring);
     source.start(this);
     connectionFactory.start(backgroundProcessor);
     endpointManager.addListener(new InstantiatorRecoveryListener(backgroundProcessor, this));
diff --git a/geode-core/src/main/java/org/apache/geode/cache/client/internal/QueueManagerImpl.java b/geode-core/src/main/java/org/apache/geode/cache/client/internal/QueueManagerImpl.java
index af187d9..03fe423 100644
--- a/geode-core/src/main/java/org/apache/geode/cache/client/internal/QueueManagerImpl.java
+++ b/geode-core/src/main/java/org/apache/geode/cache/client/internal/QueueManagerImpl.java
@@ -30,8 +30,6 @@ import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.logging.log4j.Logger;
@@ -67,6 +65,7 @@ import org.apache.geode.internal.cache.tier.sockets.ServerQueueStatus;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.InternalLogWriter;
 import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.security.GemFireSecurityException;
 
@@ -103,7 +102,7 @@ public class QueueManagerImpl implements QueueManager {
 
   protected final CountDownLatch initializedLatch = new CountDownLatch(1);
 
-  private ScheduledThreadPoolExecutor recoveryThread;
+  private ScheduledExecutorService recoveryThread;
   private volatile boolean sentClientReady;
 
   // queueConnections in maintained by using copy-on-write
@@ -278,17 +277,7 @@ public class QueueManagerImpl implements QueueManager {
       // We don't want primary recovery (and therefore user threads) to wait for
       // things like pinging connections for health checks.
       final String name = "queueTimer-" + this.pool.getName();
-      this.recoveryThread = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
-
-        public Thread newThread(Runnable r) {
-          Thread result = new Thread(r, name);
-          result.setDaemon(true);
-          return result;
-        }
-
-
-      });
-      recoveryThread.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
+      this.recoveryThread = LoggingExecutors.newScheduledThreadPool(name, 1, false);
 
       getState().start(background, getPool().getSubscriptionAckInterval());
 
diff --git a/geode-core/src/main/java/org/apache/geode/cache/client/internal/SingleHopClientExecutor.java b/geode-core/src/main/java/org/apache/geode/cache/client/internal/SingleHopClientExecutor.java
index a206a1b..f59fa7b 100644
--- a/geode-core/src/main/java/org/apache/geode/cache/client/internal/SingleHopClientExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/cache/client/internal/SingleHopClientExecutor.java
@@ -22,10 +22,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.logging.log4j.Logger;
 
@@ -46,24 +43,15 @@ import org.apache.geode.internal.cache.execute.InternalFunctionInvocationTargetE
 import org.apache.geode.internal.cache.tier.sockets.VersionedObjectList;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 
 public class SingleHopClientExecutor {
 
   private static final Logger logger = LogService.getLogger();
 
-  static final ExecutorService execService = Executors.newCachedThreadPool(new ThreadFactory() {
-    AtomicInteger threadNum = new AtomicInteger();
-
-    public Thread newThread(final Runnable r) {
-      Thread result =
-          new Thread(LoggingThreadGroup.createThreadGroup("FunctionExecutionThreadGroup", logger),
-              r, "Function Execution Thread-" + threadNum.incrementAndGet());
-      result.setDaemon(true);
-      return result;
-    }
-  });
+  static final ExecutorService execService =
+      LoggingExecutors.newCachedThreadPool("Function Execution Thread-", true);
 
   static void submitAll(List callableTasks) {
     if (callableTasks != null && !callableTasks.isEmpty()) {
diff --git a/geode-core/src/main/java/org/apache/geode/cache/client/internal/pooling/ConnectionManagerImpl.java b/geode-core/src/main/java/org/apache/geode/cache/client/internal/pooling/ConnectionManagerImpl.java
index d928e53..e5ca641 100644
--- a/geode-core/src/main/java/org/apache/geode/cache/client/internal/pooling/ConnectionManagerImpl.java
+++ b/geode-core/src/main/java/org/apache/geode/cache/client/internal/pooling/ConnectionManagerImpl.java
@@ -27,8 +27,6 @@ import java.util.Set;
 import java.util.SplittableRandom;
 import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.ReentrantLock;
@@ -60,6 +58,7 @@ import org.apache.geode.internal.cache.PoolStats;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.InternalLogWriter;
 import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.security.GemFireSecurityException;
 
@@ -91,7 +90,7 @@ public class ConnectionManagerImpl implements ConnectionManager {
 
   protected volatile int connectionCount;
   protected ScheduledExecutorService backgroundProcessor;
-  protected ScheduledThreadPoolExecutor loadConditioningProcessor;
+  protected ScheduledExecutorService loadConditioningProcessor;
 
   protected ReentrantLock lock = new ReentrantLock();
   protected Condition freeConnection = lock.newCondition();
@@ -591,15 +590,9 @@ public class ConnectionManagerImpl implements ConnectionManager {
    */
   public void start(ScheduledExecutorService backgroundProcessor) {
     this.backgroundProcessor = backgroundProcessor;
+    String name = "poolLoadConditioningMonitor-" + getPoolName();
     this.loadConditioningProcessor =
-        new ScheduledThreadPoolExecutor(1/* why not 0? */, new ThreadFactory() {
-          public Thread newThread(final Runnable r) {
-            Thread result = new Thread(r, "poolLoadConditioningMonitor-" + getPoolName());
-            result.setDaemon(true);
-            return result;
-          }
-        });
-    this.loadConditioningProcessor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
+        LoggingExecutors.newScheduledThreadPool(name, 1/* why not 0? */, false);
 
     endpointManager.addListener(endpointListener);
 
diff --git a/geode-core/src/main/java/org/apache/geode/cache/query/internal/index/IndexManager.java b/geode-core/src/main/java/org/apache/geode/cache/query/internal/index/IndexManager.java
index e3c33e8..0a7b76a 100644
--- a/geode-core/src/main/java/org/apache/geode/cache/query/internal/index/IndexManager.java
+++ b/geode-core/src/main/java/org/apache/geode/cache/query/internal/index/IndexManager.java
@@ -73,7 +73,7 @@ import org.apache.geode.internal.cache.TXManagerImpl;
 import org.apache.geode.internal.cache.TXStateProxy;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 
 public class IndexManager {
   private static final Logger logger = LogService.getLogger();
@@ -167,9 +167,7 @@ public class IndexManager {
         region.getAttributes().getEvictionAttributes().getAction().isOverflowToDisk();
     this.offHeap = region.getAttributes().getOffHeap();
     if (!indexMaintenanceSynchronous) {
-      final LoggingThreadGroup group =
-          LoggingThreadGroup.createThreadGroup("QueryMonitor Thread Group", logger);
-      updater = new IndexUpdaterThread(group, this.INDEX_MAINTENANCE_BUFFER,
+      updater = new IndexUpdaterThread(this.INDEX_MAINTENANCE_BUFFER,
           "OqlIndexUpdater:" + region.getFullPath());
       updater.start();
     }
@@ -1391,7 +1389,7 @@ public class IndexManager {
 
   ////////////////////// Inner Classes //////////////////////
 
-  public class IndexUpdaterThread extends Thread {
+  public class IndexUpdaterThread extends LoggingThread {
 
     private volatile boolean running = true;
 
@@ -1402,8 +1400,8 @@ public class IndexManager {
     /**
      * Creates instance of IndexUpdaterThread
      */
-    IndexUpdaterThread(ThreadGroup group, int updateThreshold, String threadName) {
-      super(group, threadName);
+    IndexUpdaterThread(int updateThreshold, String threadName) {
+      super(threadName);
       // Check if threshold is set.
       if (updateThreshold > 0) {
         // Create a bounded queue.
@@ -1412,7 +1410,6 @@ public class IndexManager {
         // Create non-bounded queue.
         pendingTasks = new LinkedBlockingQueue();
       }
-      this.setDaemon(true);
     }
 
     public void addTask(int action, RegionEntry entry, int opCode) {
diff --git a/geode-core/src/main/java/org/apache/geode/distributed/ServerLauncher.java b/geode-core/src/main/java/org/apache/geode/distributed/ServerLauncher.java
index 9b7be48..b6ac996 100755
--- a/geode-core/src/main/java/org/apache/geode/distributed/ServerLauncher.java
+++ b/geode-core/src/main/java/org/apache/geode/distributed/ServerLauncher.java
@@ -68,6 +68,7 @@ import org.apache.geode.internal.cache.PartitionedRegion;
 import org.apache.geode.internal.cache.tier.sockets.CacheServerHelper;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.lang.ObjectUtils;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.net.SocketCreator;
 import org.apache.geode.internal.process.ConnectionFailedException;
 import org.apache.geode.internal.process.ControlNotificationHandler;
@@ -1179,16 +1180,7 @@ public class ServerLauncher extends AbstractLauncher<String> {
       // Another case of needing to use a non-daemon thread to keep the JVM alive until a clean
       // shutdown can be performed. If not, the JVM may exit too early causing the member to be
       // seen as having crashed and not cleanly departed.
-      final ServerLauncher shadow = this;
-      Thread t = new Thread(() -> {
-        shadow.cache.close();
-        shadow.cache = null;
-        if (shadow.process != null) {
-          shadow.process.stop(shadow.deletePidFileOnStop);
-          shadow.process = null;
-        }
-      });
-      t.setDaemon(false);
+      Thread t = new LoggingThread("ServerLauncherStopper", false, this::doStopInProcess);
       t.start();
 
       try {
@@ -1205,6 +1197,15 @@ public class ServerLauncher extends AbstractLauncher<String> {
     }
   }
 
+  private void doStopInProcess() {
+    cache.close();
+    cache = null;
+    if (process != null) {
+      process.stop(deletePidFileOnStop);
+      process = null;
+    }
+  }
+
   private ServerState stopWithPid() {
     try {
       final ProcessController controller = new ProcessControllerFactory()
diff --git a/geode-core/src/main/java/org/apache/geode/distributed/internal/ClusterDistributionManager.java b/geode-core/src/main/java/org/apache/geode/distributed/internal/ClusterDistributionManager.java
index 6b503c7..f47440c 100644
--- a/geode-core/src/main/java/org/apache/geode/distributed/internal/ClusterDistributionManager.java
+++ b/geode-core/src/main/java/org/apache/geode/distributed/internal/ClusterDistributionManager.java
@@ -37,7 +37,6 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.SynchronousQueue;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
@@ -78,7 +77,9 @@ import org.apache.geode.internal.cache.InitialImageOperation;
 import org.apache.geode.internal.cache.InternalCache;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
+import org.apache.geode.internal.logging.LoggingThread;
+import org.apache.geode.internal.logging.LoggingUncaughtExceptionHandler;
 import org.apache.geode.internal.logging.log4j.AlertAppender;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.logging.log4j.LogMarker;
@@ -376,17 +377,14 @@ public class ClusterDistributionManager implements DistributionManager {
    */
   private volatile Set<InternalDistributedMember> adminConsoles = Collections.emptySet();
 
-  /** The group of distribution manager threads */
-  protected LoggingThreadGroup threadGroup;
-
   /** Message processing thread pool */
-  private ThreadPoolExecutor threadPool;
+  private ExecutorService threadPool;
 
   /**
    * High Priority processing thread pool, used for initializing messages such as UpdateAttributes
    * and CreateRegion messages
    */
-  private ThreadPoolExecutor highPriorityPool;
+  private ExecutorService highPriorityPool;
 
   /**
    * Waiting Pool, used for messages that may have to wait on something. Use this separate pool with
@@ -394,9 +392,9 @@ public class ClusterDistributionManager implements DistributionManager {
    * Used for threads that will most likely have to wait for a region to be finished initializing
    * before it can proceed
    */
-  private ThreadPoolExecutor waitingPool;
+  private ExecutorService waitingPool;
 
-  private ThreadPoolExecutor prMetaDataCleanupThreadPool;
+  private ExecutorService prMetaDataCleanupThreadPool;
 
   /**
    * Thread used to decouple {@link org.apache.geode.internal.cache.partitioned.PartitionMessage}s
@@ -404,22 +402,22 @@ public class ClusterDistributionManager implements DistributionManager {
    *
    * @see #SERIAL_EXECUTOR
    */
-  private ThreadPoolExecutor partitionedRegionThread;
-  private ThreadPoolExecutor partitionedRegionPool;
+  private ExecutorService partitionedRegionThread;
+  private ExecutorService partitionedRegionPool;
 
   /** Function Execution executors */
-  private ThreadPoolExecutor functionExecutionThread;
-  private ThreadPoolExecutor functionExecutionPool;
+  private ExecutorService functionExecutionThread;
+  private ExecutorService functionExecutionPool;
 
   /** Message processing executor for serial, ordered, messages. */
-  private ThreadPoolExecutor serialThread;
+  private ExecutorService serialThread;
 
   /**
    * Message processing executor for view messages
    *
    * @see org.apache.geode.distributed.internal.membership.gms.messages.ViewAckMessage
    */
-  private ThreadPoolExecutor viewThread;
+  private ExecutorService viewThread;
 
   /**
    * If using a throttling queue for the serialThread, we cache the queue here so we can see if
@@ -649,11 +647,6 @@ public class ClusterDistributionManager implements DistributionManager {
 
     this.exceptionInThreads = false;
 
-    // Start the processing threads
-    final LoggingThreadGroup group =
-        LoggingThreadGroup.createThreadGroup("DistributionManager Threads", logger);
-    this.threadGroup = group;
-
     {
       Properties nonDefault = new Properties();
       DistributionConfigImpl distributionConfigImpl = new DistributionConfigImpl(nonDefault);
@@ -685,8 +678,7 @@ public class ClusterDistributionManager implements DistributionManager {
         // distributed deadlock when we block the UDP reader thread
         boolean throttlingDisabled = system.getConfig().getDisableTcp();
         this.serialQueuedExecutorPool =
-            new SerialQueuedExecutorPool(this.threadGroup, this.stats, throttlingDisabled,
-                this.threadMonitor);
+            new SerialQueuedExecutorPool(this.stats, throttlingDisabled, this.threadMonitor);
       }
 
       {
@@ -700,173 +692,30 @@ public class ClusterDistributionManager implements DistributionManager {
                   this.stats.getSerialQueueHelper());
           poolQueue = this.serialQueue;
         }
-        ThreadFactory tf = new ThreadFactory() {
-          @Override
-          public Thread newThread(final Runnable command) {
-            ClusterDistributionManager.this.stats.incSerialThreadStarts();
-            final Runnable r = new Runnable() {
-              @Override
-              public void run() {
-                ClusterDistributionManager.this.stats.incNumSerialThreads(1);
-                try {
-                  ConnectionTable.threadWantsSharedResources();
-                  Connection.makeReaderThread();
-                  runUntilShutdown(command);
-                  // command.run();
-                } finally {
-                  ConnectionTable.releaseThreadsSockets();
-                  ClusterDistributionManager.this.stats.incNumSerialThreads(-1);
-                }
-              }
-            };
-            Thread thread = new Thread(group, r,
-                LocalizedStrings.DistributionManager_SERIAL_MESSAGE_PROCESSOR.toLocalizedString());
-            thread.setDaemon(true);
-            return thread;
-          }
-        };
-
-        this.serialThread = new SerialQueuedExecutorWithDMStats(poolQueue,
-            this.stats.getSerialProcessorHelper(), tf, threadMonitor);
-      }
-      {
-        BlockingQueue q = new LinkedBlockingQueue();
-        ThreadFactory tf = new ThreadFactory() {
-          @Override
-          public Thread newThread(final Runnable command) {
-            ClusterDistributionManager.this.stats.incViewThreadStarts();
-            final Runnable r = new Runnable() {
-              @Override
-              public void run() {
-                ClusterDistributionManager.this.stats.incNumViewThreads(1);
-                try {
-                  ConnectionTable.threadWantsSharedResources();
-                  Connection.makeReaderThread();
-                  runUntilShutdown(command);
-                } finally {
-                  ConnectionTable.releaseThreadsSockets();
-                  ClusterDistributionManager.this.stats.incNumViewThreads(-1);
-                }
-              }
-            };
-            Thread thread = new Thread(group, r,
-                LocalizedStrings.DistributionManager_VIEW_MESSAGE_PROCESSOR.toLocalizedString());
-            thread.setDaemon(true);
-            return thread;
-          }
-        };
-        this.viewThread = new SerialQueuedExecutorWithDMStats(q,
-            this.stats.getViewProcessorHelper(), tf, threadMonitor);
-      }
-
-      {
-        BlockingQueue<Runnable> poolQueue;
-        if (INCOMING_QUEUE_LIMIT == 0) {
-          poolQueue = new OverflowQueueWithDMStats<>(this.stats.getOverflowQueueHelper());
-        } else {
-          poolQueue = new OverflowQueueWithDMStats<>(INCOMING_QUEUE_LIMIT,
-              this.stats.getOverflowQueueHelper());
-        }
-        ThreadFactory tf = new ThreadFactory() {
-          private int next = 0;
-
-          @Override
-          public Thread newThread(final Runnable command) {
-            ClusterDistributionManager.this.stats.incProcessingThreadStarts();
-            final Runnable r = new Runnable() {
-              @Override
-              public void run() {
-                ClusterDistributionManager.this.stats.incNumProcessingThreads(1);
-                try {
-                  ConnectionTable.threadWantsSharedResources();
-                  Connection.makeReaderThread();
-                  runUntilShutdown(command);
-                } finally {
-                  ConnectionTable.releaseThreadsSockets();
-                  ClusterDistributionManager.this.stats.incNumProcessingThreads(-1);
-                }
-              }
-            };
-            Thread thread = new Thread(group, r,
-                LocalizedStrings.DistributionManager_POOLED_MESSAGE_PROCESSOR.toLocalizedString()
-                    + (next++));
-            thread.setDaemon(true);
-            return thread;
-          }
-        };
-        this.threadPool = new PooledExecutorWithDMStats(poolQueue, MAX_THREADS,
-            this.stats.getNormalPoolHelper(), tf, threadMonitor);
+        this.serialThread = LoggingExecutors.newSerialThreadPool("Serial Message Processor",
+            thread -> stats.incSerialThreadStarts(),
+            this::doSerialThread, this.stats.getSerialProcessorHelper(),
+            threadMonitor, poolQueue);
       }
 
+      this.viewThread =
+          LoggingExecutors.newSerialThreadPoolWithUnlimitedFeed("View Message Processor",
+              thread -> stats.incViewThreadStarts(), this::doViewThread,
+              this.stats.getViewProcessorHelper(), threadMonitor);
 
-      {
-        BlockingQueue<Runnable> poolQueue;
-        if (INCOMING_QUEUE_LIMIT == 0) {
-          poolQueue = new OverflowQueueWithDMStats<>(this.stats.getHighPriorityQueueHelper());
-        } else {
-          poolQueue = new OverflowQueueWithDMStats<>(INCOMING_QUEUE_LIMIT,
-              this.stats.getHighPriorityQueueHelper());
-        }
-        ThreadFactory tf = new ThreadFactory() {
-          private int next = 0;
-
-          @Override
-          public Thread newThread(final Runnable command) {
-            ClusterDistributionManager.this.stats.incHighPriorityThreadStarts();
-            final Runnable r = new Runnable() {
-              @Override
-              public void run() {
-                ClusterDistributionManager.this.stats.incHighPriorityThreads(1);
-                try {
-                  ConnectionTable.threadWantsSharedResources();
-                  Connection.makeReaderThread();
-                  runUntilShutdown(command);
-                } finally {
-                  ConnectionTable.releaseThreadsSockets();
-                  ClusterDistributionManager.this.stats.incHighPriorityThreads(-1);
-                }
-              }
-            };
-            Thread thread = new Thread(group, r,
-                LocalizedStrings.DistributionManager_POOLED_HIGH_PRIORITY_MESSAGE_PROCESSOR
-                    .toLocalizedString() + (next++));
-            thread.setDaemon(true);
-            return thread;
-          }
-        };
-        this.highPriorityPool = new PooledExecutorWithDMStats(poolQueue, MAX_THREADS,
-            this.stats.getHighPriorityPoolHelper(), tf, threadMonitor);
-      }
+      this.threadPool =
+          LoggingExecutors.newThreadPoolWithFeedStatistics("Pooled Message Processor ",
+              thread -> stats.incProcessingThreadStarts(), this::doProcessingThread,
+              MAX_THREADS, this.stats.getNormalPoolHelper(), threadMonitor,
+              INCOMING_QUEUE_LIMIT, this.stats.getOverflowQueueHelper());
 
+      this.highPriorityPool = LoggingExecutors.newThreadPoolWithFeedStatistics(
+          "Pooled High Priority Message Processor ",
+          thread -> stats.incHighPriorityThreadStarts(), this::doHighPriorityThread,
+          MAX_THREADS, this.stats.getHighPriorityPoolHelper(), threadMonitor,
+          INCOMING_QUEUE_LIMIT, this.stats.getHighPriorityQueueHelper());
 
       {
-        ThreadFactory tf = new ThreadFactory() {
-          private int next = 0;
-
-          @Override
-          public Thread newThread(final Runnable command) {
-            ClusterDistributionManager.this.stats.incWaitingThreadStarts();
-            final Runnable r = new Runnable() {
-              @Override
-              public void run() {
-                ClusterDistributionManager.this.stats.incWaitingThreads(1);
-                try {
-                  ConnectionTable.threadWantsSharedResources();
-                  Connection.makeReaderThread();
-                  runUntilShutdown(command);
-                } finally {
-                  ConnectionTable.releaseThreadsSockets();
-                  ClusterDistributionManager.this.stats.incWaitingThreads(-1);
-                }
-              }
-            };
-            Thread thread = new Thread(group, r,
-                LocalizedStrings.DistributionManager_POOLED_WAITING_MESSAGE_PROCESSOR
-                    .toLocalizedString() + (next++));
-            thread.setDaemon(true);
-            return thread;
-          }
-        };
         BlockingQueue<Runnable> poolQueue;
         if (MAX_WAITING_THREADS == Integer.MAX_VALUE) {
           // no need for a queue since we have infinite threads
@@ -874,137 +723,48 @@ public class ClusterDistributionManager implements DistributionManager {
         } else {
           poolQueue = new OverflowQueueWithDMStats<>(this.stats.getWaitingQueueHelper());
         }
-        this.waitingPool = new PooledExecutorWithDMStats(poolQueue, MAX_WAITING_THREADS,
-            this.stats.getWaitingPoolHelper(), tf, threadMonitor);
-      }
-
-      {
-        ThreadFactory tf = new ThreadFactory() {
-          private int next = 0;
-
-          @Override
-          public Thread newThread(final Runnable command) {
-            ClusterDistributionManager.this.stats.incWaitingThreadStarts();// will it be ok?
-            final Runnable r = new Runnable() {
-              @Override
-              public void run() {
-                ClusterDistributionManager.this.stats.incWaitingThreads(1);// will it be ok
-                try {
-                  ConnectionTable.threadWantsSharedResources();
-                  Connection.makeReaderThread();
-                  runUntilShutdown(command);
-                } finally {
-                  ConnectionTable.releaseThreadsSockets();
-                  ClusterDistributionManager.this.stats.incWaitingThreads(-1);
-                }
-              }
-            };
-            Thread thread = new Thread(group, r,
-                LocalizedStrings.DistributionManager_PR_META_DATA_CLEANUP_MESSAGE_PROCESSOR
-                    .toLocalizedString() + (next++));
-            thread.setDaemon(true);
-            return thread;
-          }
-        };
-        BlockingQueue<Runnable> poolQueue;
-        poolQueue = new OverflowQueueWithDMStats<>(this.stats.getWaitingQueueHelper());
-        this.prMetaDataCleanupThreadPool = new PooledExecutorWithDMStats(poolQueue,
-            MAX_PR_META_DATA_CLEANUP_THREADS, this.stats.getWaitingPoolHelper(), tf, threadMonitor);
-      }
-
-      {
-        BlockingQueue<Runnable> poolQueue;
-        if (INCOMING_QUEUE_LIMIT == 0) {
-          poolQueue = new OverflowQueueWithDMStats<>(this.stats.getPartitionedRegionQueueHelper());
-        } else {
-          poolQueue = new OverflowQueueWithDMStats<>(INCOMING_QUEUE_LIMIT,
-              this.stats.getPartitionedRegionQueueHelper());
-        }
-        ThreadFactory tf = new ThreadFactory() {
-          private int next = 0;
-
-          @Override
-          public Thread newThread(final Runnable command) {
-            ClusterDistributionManager.this.stats.incPartitionedRegionThreadStarts();
-            final Runnable r = new Runnable() {
-              @Override
-              public void run() {
-                stats.incPartitionedRegionThreads(1);
-                try {
-                  ConnectionTable.threadWantsSharedResources();
-                  Connection.makeReaderThread();
-                  runUntilShutdown(command);
-                } finally {
-                  ConnectionTable.releaseThreadsSockets();
-                  stats.incPartitionedRegionThreads(-1);
-                }
-              }
-            };
-            Thread thread = new Thread(group, r, "PartitionedRegion Message Processor" + (next++));
-            thread.setDaemon(true);
-            return thread;
-          }
-        };
-        if (MAX_PR_THREADS > 1) {
-          this.partitionedRegionPool = new PooledExecutorWithDMStats(poolQueue, MAX_PR_THREADS,
-              this.stats.getPartitionedRegionPoolHelper(), tf, threadMonitor);
-        } else {
-          this.partitionedRegionThread = new SerialQueuedExecutorWithDMStats(poolQueue,
-              this.stats.getPartitionedRegionPoolHelper(), tf, threadMonitor);
-        }
-
-      }
-
-      {
-        BlockingQueue<Runnable> poolQueue;
-        if (INCOMING_QUEUE_LIMIT == 0) {
-          poolQueue = new OverflowQueueWithDMStats<>(this.stats.getFunctionExecutionQueueHelper());
-        } else {
-          poolQueue = new OverflowQueueWithDMStats<>(INCOMING_QUEUE_LIMIT,
-              this.stats.getFunctionExecutionQueueHelper());
-        }
-        ThreadFactory tf = new ThreadFactory() {
-          private int next = 0;
-
-          @Override
-          public Thread newThread(final Runnable command) {
-            ClusterDistributionManager.this.stats.incFunctionExecutionThreadStarts();
-            final Runnable r = new Runnable() {
-              @Override
-              public void run() {
-                stats.incFunctionExecutionThreads(1);
-                isFunctionExecutionThread.set(Boolean.TRUE);
-                try {
-                  ConnectionTable.threadWantsSharedResources();
-                  Connection.makeReaderThread();
-                  runUntilShutdown(command);
-                } finally {
-                  ConnectionTable.releaseThreadsSockets();
-                  stats.incFunctionExecutionThreads(-1);
-                }
-              }
-            };
-            Thread thread = new Thread(group, r, "Function Execution Processor" + (next++));
-            thread.setDaemon(true);
-            return thread;
-          }
-        };
-
-        if (MAX_FE_THREADS > 1) {
-          this.functionExecutionPool = new FunctionExecutionPooledExecutor(poolQueue,
-              MAX_FE_THREADS, this.stats.getFunctionExecutionPoolHelper(), tf,
-              true /* for fn exec */, this.threadMonitor);
-        } else {
-          this.functionExecutionThread = new SerialQueuedExecutorWithDMStats(poolQueue,
-              this.stats.getFunctionExecutionPoolHelper(), tf, threadMonitor);
-        }
-
+        this.waitingPool = LoggingExecutors.newThreadPool("Pooled Waiting Message Processor ",
+            thread -> stats.incWaitingThreadStarts(), this::doWaitingThread,
+            MAX_WAITING_THREADS, this.stats.getWaitingPoolHelper(), threadMonitor, poolQueue);
+      }
+
+      // should this pool using the waiting pool stats?
+      this.prMetaDataCleanupThreadPool =
+          LoggingExecutors.newThreadPoolWithFeedStatistics("PrMetaData cleanup Message Processor ",
+              thread -> stats.incWaitingThreadStarts(), this::doWaitingThread,
+              MAX_PR_META_DATA_CLEANUP_THREADS, this.stats.getWaitingPoolHelper(), threadMonitor,
+              0, this.stats.getWaitingQueueHelper());
+
+      if (MAX_PR_THREADS > 1) {
+        this.partitionedRegionPool =
+            LoggingExecutors.newThreadPoolWithFeedStatistics("PartitionedRegion Message Processor",
+                thread -> stats.incPartitionedRegionThreadStarts(), this::doPartitionRegionThread,
+                MAX_PR_THREADS, this.stats.getPartitionedRegionPoolHelper(), threadMonitor,
+                INCOMING_QUEUE_LIMIT, this.stats.getPartitionedRegionQueueHelper());
+      } else {
+        this.partitionedRegionThread = LoggingExecutors.newSerialThreadPoolWithFeedStatistics(
+            "PartitionedRegion Message Processor",
+            thread -> stats.incPartitionedRegionThreadStarts(), this::doPartitionRegionThread,
+            this.stats.getPartitionedRegionPoolHelper(), threadMonitor,
+            INCOMING_QUEUE_LIMIT, this.stats.getPartitionedRegionQueueHelper());
+      }
+      if (MAX_FE_THREADS > 1) {
+        this.functionExecutionPool =
+            LoggingExecutors.newFunctionThreadPoolWithFeedStatistics("Function Execution Processor",
+                thread -> stats.incFunctionExecutionThreadStarts(), this::doFunctionExecutionThread,
+                MAX_FE_THREADS, this.stats.getFunctionExecutionPoolHelper(), threadMonitor,
+                INCOMING_QUEUE_LIMIT, this.stats.getFunctionExecutionQueueHelper());
+      } else {
+        this.functionExecutionThread =
+            LoggingExecutors.newSerialThreadPoolWithFeedStatistics("Function Execution Processor",
+                thread -> stats.incFunctionExecutionThreadStarts(), this::doFunctionExecutionThread,
+                this.stats.getFunctionExecutionPoolHelper(), threadMonitor,
+                INCOMING_QUEUE_LIMIT, this.stats.getFunctionExecutionQueueHelper());
       }
 
       if (!SYNC_EVENTS) {
         this.memberEventThread =
-            new Thread(group, new MemberEventInvoker(), "DM-MemberEventInvoker");
-        this.memberEventThread.setDaemon(true);
+            new LoggingThread("DM-MemberEventInvoker", new MemberEventInvoker());
       }
 
       StringBuffer sb = new StringBuffer(" (took ");
@@ -1040,6 +800,91 @@ public class ClusterDistributionManager implements DistributionManager {
     }
   }
 
+  private void doFunctionExecutionThread(Runnable command) {
+    stats.incFunctionExecutionThreads(1);
+    isFunctionExecutionThread.set(Boolean.TRUE);
+    try {
+      ConnectionTable.threadWantsSharedResources();
+      Connection.makeReaderThread();
+      runUntilShutdown(command);
+    } finally {
+      ConnectionTable.releaseThreadsSockets();
+      stats.incFunctionExecutionThreads(-1);
+    }
+  }
+
+  private void doProcessingThread(Runnable command) {
+    stats.incNumProcessingThreads(1);
+    try {
+      ConnectionTable.threadWantsSharedResources();
+      Connection.makeReaderThread();
+      runUntilShutdown(command);
+    } finally {
+      ConnectionTable.releaseThreadsSockets();
+      stats.incNumProcessingThreads(-1);
+    }
+  }
+
+  private void doHighPriorityThread(Runnable command) {
+    stats.incHighPriorityThreads(1);
+    try {
+      ConnectionTable.threadWantsSharedResources();
+      Connection.makeReaderThread();
+      runUntilShutdown(command);
+    } finally {
+      ConnectionTable.releaseThreadsSockets();
+      stats.incHighPriorityThreads(-1);
+    }
+  }
+
+  private void doWaitingThread(Runnable command) {
+    stats.incWaitingThreads(1);
+    try {
+      ConnectionTable.threadWantsSharedResources();
+      Connection.makeReaderThread();
+      runUntilShutdown(command);
+    } finally {
+      ConnectionTable.releaseThreadsSockets();
+      stats.incWaitingThreads(-1);
+    }
+  }
+
+  private void doPartitionRegionThread(Runnable command) {
+    stats.incPartitionedRegionThreads(1);
+    try {
+      ConnectionTable.threadWantsSharedResources();
+      Connection.makeReaderThread();
+      runUntilShutdown(command);
+    } finally {
+      ConnectionTable.releaseThreadsSockets();
+      stats.incPartitionedRegionThreads(-1);
+    }
+  }
+
+  private void doViewThread(Runnable command) {
+    stats.incNumViewThreads(1);
+    try {
+      ConnectionTable.threadWantsSharedResources();
+      Connection.makeReaderThread();
+      runUntilShutdown(command);
+    } finally {
+      ConnectionTable.releaseThreadsSockets();
+      stats.incNumViewThreads(-1);
+    }
+  }
+
+  private void doSerialThread(Runnable command) {
+    stats.incNumSerialThreads(1);
+    try {
+      ConnectionTable.threadWantsSharedResources();
+      Connection.makeReaderThread();
+      runUntilShutdown(command);
+    } finally {
+      ConnectionTable.releaseThreadsSockets();
+      stats.incNumSerialThreads(-1);
+    }
+  }
+
   /**
    * Creates a new distribution manager
    *
@@ -1396,7 +1241,8 @@ public class ClusterDistributionManager implements DistributionManager {
    */
   @Override
   public boolean exceptionInThreads() {
-    return this.exceptionInThreads || this.threadGroup.getUncaughtExceptionsCount() > 0;
+    return this.exceptionInThreads
+        || LoggingUncaughtExceptionHandler.getUncaughtExceptionsCount() > 0;
   }
 
   /**
@@ -1406,7 +1252,7 @@ public class ClusterDistributionManager implements DistributionManager {
   @Override
   public void clearExceptionInThreads() {
     this.exceptionInThreads = false;
-    this.threadGroup.clearUncaughtExceptionsCount();
+    LoggingUncaughtExceptionHandler.clearUncaughtExceptionsCount();
   }
 
   /**
@@ -1739,9 +1585,9 @@ public class ClusterDistributionManager implements DistributionManager {
             }
           }
         };
-        final Thread t = new Thread(threadGroup, r,
-            LocalizedStrings.DistributionManager_SHUTDOWN_MESSAGE_THREAD_FOR_0
-                .toLocalizedString(this.localAddress));
+        final Thread t =
+            new LoggingThread(LocalizedStrings.DistributionManager_SHUTDOWN_MESSAGE_THREAD_FOR_0
+                .toLocalizedString(this.localAddress), false, r);
         t.start();
         boolean interrupted = Thread.interrupted();
         try {
@@ -1907,11 +1753,11 @@ public class ClusterDistributionManager implements DistributionManager {
    *
    * @return true if executor is still active
    */
-  private boolean executorAlive(ThreadPoolExecutor tpe, String name) {
+  private boolean executorAlive(ExecutorService tpe, String name) {
     if (tpe == null) {
       return false;
     } else {
-      int ac = tpe.getActiveCount();
+      int ac = ((ThreadPoolExecutor) tpe).getActiveCount();
       // boolean result = tpe.getActiveCount() > 0;
       if (ac > 0) {
         if (logger.isDebugEnabled()) {
@@ -3344,7 +3190,7 @@ public class ClusterDistributionManager implements DistributionManager {
    */
   private static class SerialQueuedExecutorPool {
     /** To store the serial threads */
-    final ConcurrentMap<Integer, SerialQueuedExecutorWithDMStats> serialQueuedExecutorMap =
+    final ConcurrentMap<Integer, ExecutorService> serialQueuedExecutorMap =
         new ConcurrentHashMap<>(MAX_SERIAL_QUEUE_THREAD);
 
     /** To store the queue associated with thread */
@@ -3361,20 +3207,13 @@ public class ClusterDistributionManager implements DistributionManager {
     final ArrayList<Integer> threadMarkedForUse = new ArrayList<>();
 
     final DistributionStats stats;
-    final ThreadGroup threadGroup;
 
     final boolean throttlingDisabled;
 
     final ThreadsMonitoring threadMonitoring;
 
-    /**
-     * Constructor.
-     *
-     * @param group thread group to which the threads will belog to.
-     */
-    SerialQueuedExecutorPool(ThreadGroup group, DistributionStats stats,
+    SerialQueuedExecutorPool(DistributionStats stats,
         boolean throttlingDisabled, ThreadsMonitoring tMonitoring) {
-      this.threadGroup = group;
       this.stats = stats;
       this.throttlingDisabled = throttlingDisabled;
       this.threadMonitoring = tMonitoring;
@@ -3430,9 +3269,9 @@ public class ClusterDistributionManager implements DistributionManager {
      * applied during put event, this doesnt block the extract operation on the queue.
      *
      */
-    SerialQueuedExecutorWithDMStats getThrottledSerialExecutor(
+    ExecutorService getThrottledSerialExecutor(
         InternalDistributedMember sender) {
-      SerialQueuedExecutorWithDMStats executor = getSerialExecutor(sender);
+      ExecutorService executor = getSerialExecutor(sender);
 
       // Get the total serial queue size.
       int totalSerialQueueMemSize = stats.getSerialQueueBytes();
@@ -3469,8 +3308,8 @@ public class ClusterDistributionManager implements DistributionManager {
     /*
      * Returns the serial queue executor for the given sender.
      */
-    SerialQueuedExecutorWithDMStats getSerialExecutor(InternalDistributedMember sender) {
-      SerialQueuedExecutorWithDMStats executor = null;
+    ExecutorService getSerialExecutor(InternalDistributedMember sender) {
+      ExecutorService executor = null;
       Integer queueId = getQueueId(sender, true);
       if ((executor =
           serialQueuedExecutorMap.get(queueId)) != null) {
@@ -3493,7 +3332,7 @@ public class ClusterDistributionManager implements DistributionManager {
     /*
      * Creates a serial queue executor.
      */
-    private SerialQueuedExecutorWithDMStats createSerialExecutor(final Integer id) {
+    private ExecutorService createSerialExecutor(final Integer id) {
 
       OverflowQueueWithDMStats<Runnable> poolQueue;
 
@@ -3507,30 +3346,19 @@ public class ClusterDistributionManager implements DistributionManager {
 
       serialQueuedMap.put(id, poolQueue);
 
-      ThreadFactory tf = new ThreadFactory() {
-        @Override
-        public Thread newThread(final Runnable command) {
-          SerialQueuedExecutorPool.this.stats.incSerialPooledThreadStarts();
-          final Runnable r = new Runnable() {
-            @Override
-            public void run() {
-              ConnectionTable.threadWantsSharedResources();
-              Connection.makeReaderThread();
-              try {
-                command.run();
-              } finally {
-                ConnectionTable.releaseThreadsSockets();
-              }
-            }
-          };
+      return LoggingExecutors.newSerialThreadPool("Pooled Serial Message Processor" + id + "-",
+          thread -> stats.incSerialPooledThreadStarts(), this::doSerialPooledThread,
+          this.stats.getSerialPooledProcessorHelper(), threadMonitoring, poolQueue);
+    }
 
-          Thread thread = new Thread(threadGroup, r, "Pooled Serial Message Processor " + id);
-          thread.setDaemon(true);
-          return thread;
-        }
-      };
-      return new SerialQueuedExecutorWithDMStats(poolQueue,
-          this.stats.getSerialPooledProcessorHelper(), tf, this.threadMonitoring);
+    private void doSerialPooledThread(Runnable command) {
+      ConnectionTable.threadWantsSharedResources();
+      Connection.makeReaderThread();
+      try {
+        command.run();
+      } finally {
+        ConnectionTable.releaseThreadsSockets();
+      }
     }
 
     /*
diff --git a/geode-core/src/main/java/org/apache/geode/distributed/internal/HealthMonitorImpl.java b/geode-core/src/main/java/org/apache/geode/distributed/internal/HealthMonitorImpl.java
index 3ce6834..e7faee8 100644
--- a/geode-core/src/main/java/org/apache/geode/distributed/internal/HealthMonitorImpl.java
+++ b/geode-core/src/main/java/org/apache/geode/distributed/internal/HealthMonitorImpl.java
@@ -24,7 +24,7 @@ import org.apache.geode.distributed.internal.membership.InternalDistributedMembe
 import org.apache.geode.internal.admin.remote.HealthListenerMessage;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 
 /**
@@ -62,10 +62,9 @@ public class HealthMonitorImpl implements HealthMonitor, Runnable {
     this.dm = dm;
     this.eval = new GemFireHealthEvaluator(config, dm);
     this.currentStatus = GemFireHealth.GOOD_HEALTH;
-    ThreadGroup tg = LoggingThreadGroup.createThreadGroup("HealthMonitor Threads", logger);
-    this.t = new Thread(tg, this,
-        LocalizedStrings.HealthMonitorImpl_HEALTH_MONITOR_OWNED_BY_0.toLocalizedString(owner));
-    this.t.setDaemon(true);
+    String threadName =
+        LocalizedStrings.HealthMonitorImpl_HEALTH_MONITOR_OWNED_BY_0.toLocalizedString(owner);
+    this.t = new LoggingThread(threadName, this);
   }
 
   /************** HealthMonitor interface implementation ******************/
diff --git a/geode-core/src/main/java/org/apache/geode/distributed/internal/InternalDistributedSystem.java b/geode-core/src/main/java/org/apache/geode/distributed/internal/InternalDistributedSystem.java
index 9d992a1..611bf84 100644
--- a/geode-core/src/main/java/org/apache/geode/distributed/internal/InternalDistributedSystem.java
+++ b/geode-core/src/main/java/org/apache/geode/distributed/internal/InternalDistributedSystem.java
@@ -94,7 +94,7 @@ import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.InternalLogWriter;
 import org.apache.geode.internal.logging.LogService;
 import org.apache.geode.internal.logging.LogWriterFactory;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.AlertAppender;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.logging.log4j.LogWriterAppender;
@@ -261,9 +261,7 @@ public class InternalDistributedSystem extends DistributedSystem
 
 
   /**
-   * A Constant that matches the ThreadGroup name of the shutdown hook. This constant is used to
-   * insure consistency with LoggingThreadGroup. Due to Bug 38407, be careful about moving this to
-   * another class.
+   * Due to Bug 38407, be careful about moving this to another class.
    */
   public static final String SHUTDOWN_HOOK_NAME = "Distributed system shutdown hook";
   /**
@@ -359,8 +357,6 @@ public class InternalDistributedSystem extends DistributedSystem
       return newSystem;
     } finally {
       if (!success) {
-        // bug44365 - logwriters accumulate, causing mem leak
-        LoggingThreadGroup.cleanUpThreadGroups();
         SystemFailure.stopThreads();
       }
     }
@@ -1058,7 +1054,7 @@ public class InternalDistributedSystem extends DistributedSystem
    *
    * @param dc the listener to run
    */
-  private void runDisconnect(final DisconnectListener dc, ThreadGroup tg) {
+  private void runDisconnect(final DisconnectListener dc) {
     // Create a general handler for running the disconnect
     Runnable r = new Runnable() {
       public void run() {
@@ -1075,7 +1071,7 @@ public class InternalDistributedSystem extends DistributedSystem
     };
 
     // Launch it and wait a little bit
-    Thread t = new Thread(tg, r, dc.toString());
+    Thread t = new LoggingThread(dc.toString(), false, r);
     try {
       t.start();
       t.join(MAX_DISCONNECT_WAIT);
@@ -1119,10 +1115,9 @@ public class InternalDistributedSystem extends DistributedSystem
    * Run a disconnect listener in the same thread sequence as the reconnect.
    *
    * @param dc the listener to run
-   * @param tg the thread group to run the listener in
    */
 
-  private void runDisconnectForReconnect(final DisconnectListener dc, ThreadGroup tg) {
+  private void runDisconnectForReconnect(final DisconnectListener dc) {
     try {
       dc.onDisconnect(InternalDistributedSystem.this);
     } catch (DistributedSystemDisconnectedException e) {
@@ -1134,12 +1129,6 @@ public class InternalDistributedSystem extends DistributedSystem
   }
 
   /**
-   * A logging thread group for the disconnect and shutdown listeners
-   */
-  private final ThreadGroup disconnectListenerThreadGroup =
-      LoggingThreadGroup.createThreadGroup("Disconnect Listeners");
-
-  /**
    * Disconnect cache, run disconnect listeners.
    *
    * @param doReconnect whether a reconnect will be done
@@ -1166,9 +1155,9 @@ public class InternalDistributedSystem extends DistributedSystem
       } // synchronized
 
       if (doReconnect) {
-        runDisconnectForReconnect(listener, disconnectListenerThreadGroup);
+        runDisconnectForReconnect(listener);
       } else {
-        runDisconnect(listener, disconnectListenerThreadGroup);
+        runDisconnect(listener);
       }
     } // for
     return shutdownListeners;
@@ -1229,8 +1218,7 @@ public class InternalDistributedSystem extends DistributedSystem
         }
       }
 
-      // Run the disconnect
-      runDisconnect(dcListener, disconnectListenerThreadGroup);
+      runDisconnect(dcListener);
 
       // Run the shutdown, if any
       if (sdListener != null) {
@@ -1476,10 +1464,6 @@ public class InternalDistributedSystem extends DistributedSystem
         }
       }
 
-      // NOTE: no logging after this point :-)
-
-      LoggingThreadGroup.cleanUpThreadGroups(); // bug35388 - logwriters accumulate, causing mem
-                                                // leak
       EventID.unsetDS();
 
     } finally {
@@ -2315,40 +2299,36 @@ public class InternalDistributedSystem extends DistributedSystem
   static {
     // Create a shutdown hook to cleanly close connection if
     // VM shuts down with an open connection.
-    ThreadGroup tg = LoggingThreadGroup.createThreadGroup(SHUTDOWN_HOOK_NAME);
     Thread tmp_shutdownHook = null;
     try {
       // Added for bug 38407
       if (!Boolean.getBoolean(DISABLE_SHUTDOWN_HOOK_PROPERTY)) {
-        tmp_shutdownHook = new Thread(tg, new Runnable() {
-          public void run() {
-            DistributedSystem ds = InternalDistributedSystem.getAnyInstance();
-            setThreadsSocketPolicy(true /* conserve sockets */);
-            if (ds != null && ds.isConnected()) {
-              LogWriterI18n log = ((InternalDistributedSystem) ds).getInternalLogWriter();
-              log.info(LocalizedStrings.InternalDistributedSystem_shutdownHook_shuttingdown);
-              DurableClientAttributes dca = ((InternalDistributedSystem) ds).getDistributedMember()
-                  .getDurableClientAttributes();
-              boolean isDurableClient = false;
-
-              if (dca != null) {
-                isDurableClient = (!(dca.getId() == null || dca.getId().isEmpty()));
-              }
-
-              ((InternalDistributedSystem) ds).disconnect(false,
-                  LocalizedStrings.InternalDistributedSystem_NORMAL_DISCONNECT.toLocalizedString(),
-                  isDurableClient/* keep alive drive from this */);
-              // this was how we wanted to do it for 5.7, but there were shutdown
-              // issues in PR/dlock (see bug 39287)
-              // InternalDistributedSystem ids = (InternalDistributedSystem)ds;
-              // if (ids.getDistributionManager() != null &&
-              // ids.getDistributionManager().getMembershipManager() != null) {
-              // ids.getDistributionManager().getMembershipManager()
-              // .uncleanShutdown("VM is exiting", null);
-              // }
+        tmp_shutdownHook = new LoggingThread(SHUTDOWN_HOOK_NAME, false, () -> {
+          DistributedSystem ds = InternalDistributedSystem.getAnyInstance();
+          setThreadsSocketPolicy(true /* conserve sockets */);
+          if (ds != null && ds.isConnected()) {
+            logger.info(LocalizedStrings.InternalDistributedSystem_shutdownHook_shuttingdown);
+            DurableClientAttributes dca = ((InternalDistributedSystem) ds).getDistributedMember()
+                .getDurableClientAttributes();
+            boolean isDurableClient = false;
+
+            if (dca != null) {
+              isDurableClient = (!(dca.getId() == null || dca.getId().isEmpty()));
             }
+
+            ((InternalDistributedSystem) ds).disconnect(false,
+                LocalizedStrings.InternalDistributedSystem_NORMAL_DISCONNECT.toLocalizedString(),
+                isDurableClient/* keep alive drive from this */);
+            // this was how we wanted to do it for 5.7, but there were shutdown
+            // issues in PR/dlock (see bug 39287)
+            // InternalDistributedSystem ids = (InternalDistributedSystem)ds;
+            // if (ids.getDistributionManager() != null &&
+            // ids.getDistributionManager().getMembershipManager() != null) {
+            // ids.getDistributionManager().getMembershipManager()
+            // .uncleanShutdown("VM is exiting", null);
+            // }
           }
-        }, SHUTDOWN_HOOK_NAME);
+        });
         Runtime.getRuntime().addShutdownHook(tmp_shutdownHook);
       }
     } finally {
diff --git a/geode-core/src/main/java/org/apache/geode/distributed/internal/InternalLocator.java b/geode-core/src/main/java/org/apache/geode/distributed/internal/InternalLocator.java
index 2ed7b9a..0ec23cc 100644
--- a/geode-core/src/main/java/org/apache/geode/distributed/internal/InternalLocator.java
+++ b/geode-core/src/main/java/org/apache/geode/distributed/internal/InternalLocator.java
@@ -69,7 +69,7 @@ import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.InternalLogWriter;
 import org.apache.geode.internal.logging.LogService;
 import org.apache.geode.internal.logging.LogWriterFactory;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.logging.log4j.LogWriterAppenders;
 import org.apache.geode.internal.net.SocketCreator;
@@ -475,11 +475,10 @@ public class InternalLocator extends Locator implements ConnectListener {
     }
     this.handler = new PrimaryHandler(this, locatorListener);
 
-    ThreadGroup group = LoggingThreadGroup.createThreadGroup("Distribution locators", logger);
     this.stats = new LocatorStats();
 
     this.server = new TcpServerFactory().makeTcpServer(port, this.bindAddress, null, this.config,
-        this.handler, new DelayedPoolStatHelper(), group, this.toString(), this);
+        this.handler, new DelayedPoolStatHelper(), this.toString(), this);
   }
 
   private void startTcpServer() throws IOException {
@@ -920,30 +919,24 @@ public class InternalLocator extends Locator implements ConnectListener {
    * launch a thread that will restart location services
    */
   private void launchRestartThread() {
-    // create a thread group having a last-chance exception-handler
-    ThreadGroup group = LoggingThreadGroup.createThreadGroup("Locator restart thread group");
-    // TODO: non-atomic operation on volatile field restartThread
-    this.restartThread = new Thread(group, "Location services restart thread") {
-      @Override
-      public void run() {
-        boolean restarted = false;
-        try {
-          restarted = attemptReconnect();
-          logger.info("attemptReconnect returned {}", restarted);
-        } catch (InterruptedException e) {
-          logger.info("attempt to restart location services was interrupted", e);
-        } catch (IOException e) {
-          logger.info("attempt to restart location services terminated", e);
-        } finally {
-          if (!restarted) {
-            stoppedForReconnect = false;
-          }
-          reconnected = restarted;
+    String threadName = "Location services restart thread";
+    this.restartThread = new LoggingThread(threadName, () -> {
+      boolean restarted = false;
+      try {
+        restarted = attemptReconnect();
+        logger.info("attemptReconnect returned {}", restarted);
+      } catch (InterruptedException e) {
+        logger.info("attempt to restart location services was interrupted", e);
+      } catch (IOException e) {
+        logger.info("attempt to restart location services terminated", e);
+      } finally {
+        if (!restarted) {
+          stoppedForReconnect = false;
         }
-        InternalLocator.this.restartThread = null;
+        reconnected = restarted;
       }
-    };
-    this.restartThread.setDaemon(true);
+      InternalLocator.this.restartThread = null;
+    });
     this.restartThread.start();
   }
 
diff --git a/geode-core/src/main/java/org/apache/geode/distributed/internal/LocatorLoadSnapshot.java b/geode-core/src/main/java/org/apache/geode/distributed/internal/LocatorLoadSnapshot.java
index ee15060..9ce9904 100644
--- a/geode-core/src/main/java/org/apache/geode/distributed/internal/LocatorLoadSnapshot.java
+++ b/geode-core/src/main/java/org/apache/geode/distributed/internal/LocatorLoadSnapshot.java
@@ -28,14 +28,14 @@ import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.geode.cache.server.ServerLoad;
 import org.apache.geode.cache.wan.GatewayReceiver;
 import org.apache.geode.internal.cache.tier.sockets.ClientProxyMembershipID;
+import org.apache.geode.internal.logging.LoggingExecutors;
 
 /**
  * A data structure used to hold load information for a locator
@@ -74,20 +74,12 @@ public class LocatorLoadSnapshot {
    */
   private boolean rebalancing;
 
-  private final ScheduledThreadPoolExecutor estimateTimeoutProcessor =
-      new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
-        public Thread newThread(final Runnable r) {
-          Thread result = new Thread(r, "loadEstimateTimeoutProcessor");
-          result.setDaemon(true);
-          return result;
-        }
-      });
-
+  private final ScheduledExecutorService estimateTimeoutProcessor =
+      LoggingExecutors.newScheduledThreadPool("loadEstimateTimeoutProcessor", 1, false);
 
   public LocatorLoadSnapshot() {
     connectionLoadMap.put(null, new HashMap());
     queueLoadMap.put(null, new HashMap());
-    this.estimateTimeoutProcessor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
     String property = System.getProperty(LOAD_IMBALANCE_THRESHOLD_PROPERTY_NAME);
     if (property != null) {
       loadImbalanceThreshold = Float.parseFloat(property);
diff --git a/geode-core/src/main/java/org/apache/geode/distributed/internal/LonerDistributionManager.java b/geode-core/src/main/java/org/apache/geode/distributed/internal/LonerDistributionManager.java
index 6115f7c..ae61799 100644
--- a/geode-core/src/main/java/org/apache/geode/distributed/internal/LonerDistributionManager.java
+++ b/geode-core/src/main/java/org/apache/geode/distributed/internal/LonerDistributionManager.java
@@ -32,7 +32,6 @@ import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -53,6 +52,7 @@ import org.apache.geode.internal.Version;
 import org.apache.geode.internal.cache.InternalCache;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.InternalLogWriter;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.internal.monitoring.ThreadsMonitoring;
 import org.apache.geode.internal.monitoring.ThreadsMonitoringImpl;
 import org.apache.geode.internal.monitoring.ThreadsMonitoringImplDummy;
@@ -128,7 +128,8 @@ public class LonerDistributionManager implements DistributionManager {
   private ConcurrentMap<InternalDistributedMember, InternalDistributedMember> canonicalIds =
       new ConcurrentHashMap<>();
   private static final DummyDMStats stats = new DummyDMStats();
-  private final ExecutorService executor = Executors.newCachedThreadPool();
+  private final ExecutorService executor =
+      LoggingExecutors.newCachedThreadPool("LonerDistributionManagerThread", false);
 
   @Override
   public long cacheTimeMillis() {
diff --git a/geode-core/src/main/java/org/apache/geode/distributed/internal/locks/DLockGrantor.java b/geode-core/src/main/java/org/apache/geode/distributed/internal/locks/DLockGrantor.java
index a6e4a01..494a5a3 100644
--- a/geode-core/src/main/java/org/apache/geode/distributed/internal/locks/DLockGrantor.java
+++ b/geode-core/src/main/java/org/apache/geode/distributed/internal/locks/DLockGrantor.java
@@ -46,6 +46,7 @@ import org.apache.geode.internal.cache.IdentityArrayList;
 import org.apache.geode.internal.cache.TXReservationMgr;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.logging.log4j.LogMarker;
 import org.apache.geode.internal.util.concurrent.StoppableCountDownLatch;
@@ -3271,7 +3272,7 @@ public class DLockGrantor {
   /**
    * Thread dedicated to handling background tasks for this grantor.
    */
-  private static class DLockGrantorThread extends Thread {
+  private static class DLockGrantorThread extends LoggingThread {
     private static final long MAX_WAIT = 60 * 1000; // 60 seconds...
     private volatile boolean shutdown = false;
     private boolean waiting = false;
@@ -3290,8 +3291,7 @@ public class DLockGrantor {
     private long nextExpire = DLockGrantorThread.MAX_WAIT;
 
     DLockGrantorThread(DLockGrantor grantor, CancelCriterion stopper) {
-      super(DLockService.getThreadGroup(), "Lock Grantor for " + grantor.dlock.getName());
-      setDaemon(true);
+      super("Lock Grantor for " + grantor.dlock.getName());
       this.grantor = grantor;
       this.stopper = stopper;
     }
@@ -3495,6 +3495,9 @@ public class DLockGrantor {
             stats.endGrantorThread(statStart);
           }
 
+        } catch (LockGrantorDestroyedException ex) {
+          this.shutdown = true;
+          return;
         } catch (InterruptedException e) {
           // shutdown probably interrupted us
 
diff --git a/geode-core/src/main/java/org/apache/geode/distributed/internal/locks/DLockService.java b/geode-core/src/main/java/org/apache/geode/distributed/internal/locks/DLockService.java
index ce11037..a0cab85 100644
--- a/geode-core/src/main/java/org/apache/geode/distributed/internal/locks/DLockService.java
+++ b/geode-core/src/main/java/org/apache/geode/distributed/internal/locks/DLockService.java
@@ -57,7 +57,6 @@ import org.apache.geode.internal.OSProcess;
 import org.apache.geode.internal.Version;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.logging.log4j.LogMarker;
 import org.apache.geode.internal.util.StopWatch;
@@ -2710,10 +2709,6 @@ public class DLockService extends DistributedLockService {
     synchronized (creationLock) {
       synchronized (services) { // disconnectListener syncs on this
         ds.getCancelCriterion().checkCancelInProgress(null);
-
-        // make sure thread group is ready...
-        readyThreadGroup(ds);
-
         if (services.get(serviceName) != null) {
           throw new IllegalArgumentException(
               LocalizedStrings.DLockService_SERVICE_NAMED_0_ALREADY_CREATED
@@ -2957,7 +2952,6 @@ public class DLockService extends DistributedLockService {
       ((DLockStats) stats).close();
       stats = DUMMY_STATS;
     }
-    threadGroup = null;
   }
 
   /** Provide way to peek at current lock grantor id when dls does not exist */
@@ -2969,21 +2963,6 @@ public class DLockService extends DistributedLockService {
     return gi;
   }
 
-  // -------------------------------------------------------------------------
-  // Internal
-  // -------------------------------------------------------------------------
-
-  protected static synchronized void readyThreadGroup(InternalDistributedSystem ds) {
-    if (threadGroup == null) {
-      Assert.assertTrue(ds != null, "Cannot find any instance of InternalDistributedSystem");
-      String threadGroupName =
-          LocalizedStrings.DLockService_DISTRIBUTED_LOCKING_THREADS.toLocalizedString();
-      final ThreadGroup group = LoggingThreadGroup.createThreadGroup(threadGroupName);
-      threadGroup = group;
-    }
-  }
-
-  // -------------------------------------------------------------------------
   // SuspendLockingToken inner class
   // -------------------------------------------------------------------------
 
diff --git a/geode-core/src/main/java/org/apache/geode/distributed/internal/membership/gms/Services.java b/geode-core/src/main/java/org/apache/geode/distributed/internal/membership/gms/Services.java
index eb73fe6..fa0b14a 100644
--- a/geode-core/src/main/java/org/apache/geode/distributed/internal/membership/gms/Services.java
+++ b/geode-core/src/main/java/org/apache/geode/distributed/internal/membership/gms/Services.java
@@ -43,7 +43,6 @@ import org.apache.geode.distributed.internal.membership.gms.mgr.GMSMembershipMan
 import org.apache.geode.internal.admin.remote.RemoteTransportConfig;
 import org.apache.geode.internal.logging.InternalLogWriter;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
 import org.apache.geode.internal.security.SecurityService;
 import org.apache.geode.internal.security.SecurityServiceFactory;
 import org.apache.geode.security.AuthenticationFailedException;
@@ -53,9 +52,6 @@ public class Services {
 
   private static final Logger logger = LogService.getLogger();
 
-  private static final ThreadGroup threadGroup =
-      LoggingThreadGroup.createThreadGroup("GemFire Membership", logger);
-
   private static InternalLogWriter staticLogWriter;
   private static InternalLogWriter staticSecurityLogWriter;
 
@@ -88,13 +84,6 @@ public class Services {
   }
 
   /**
-   * The thread group for all membership threads
-   */
-  public static ThreadGroup getThreadGroup() {
-    return threadGroup;
-  }
-
-  /**
    * a timer used for membership tasks
    */
   public Timer getTimer() {
diff --git a/geode-core/src/main/java/org/apache/geode/distributed/internal/membership/gms/fd/GMSHealthMonitor.java b/geode-core/src/main/java/org/apache/geode/distributed/internal/membership/gms/fd/GMSHealthMonitor.java
index b31d0fd..d5e5a39 100644
--- a/geode-core/src/main/java/org/apache/geode/distributed/internal/membership/gms/fd/GMSHealthMonitor.java
+++ b/geode-core/src/main/java/org/apache/geode/distributed/internal/membership/gms/fd/GMSHealthMonitor.java
@@ -44,11 +44,9 @@ import java.util.TimerTask;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
@@ -77,6 +75,7 @@ import org.apache.geode.distributed.internal.membership.gms.messages.SuspectMemb
 import org.apache.geode.distributed.internal.membership.gms.messages.SuspectRequest;
 import org.apache.geode.internal.ConnectionWatcher;
 import org.apache.geode.internal.Version;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.internal.net.SocketCreatorFactory;
 import org.apache.geode.internal.security.SecurableCommunicationChannel;
 
@@ -610,41 +609,13 @@ public class GMSHealthMonitor implements HealthMonitor, MessageHandler {
   }
 
   public void start() {
-    scheduler = Executors.newScheduledThreadPool(1, r -> {
-      Thread th = new Thread(Services.getThreadGroup(), r, "Geode Failure Detection Scheduler");
-      th.setDaemon(true);
-      return th;
-    });
-
-    checkExecutor = Executors.newCachedThreadPool(new ThreadFactory() {
-      final AtomicInteger threadIdx = new AtomicInteger();
-
-      @Override
-      public Thread newThread(Runnable r) {
-        int id = threadIdx.getAndIncrement();
-        Thread th =
-            new Thread(Services.getThreadGroup(), r, "Geode Failure Detection thread " + id);
-        th.setDaemon(true);
-        return th;
-      }
-    });
+    scheduler = LoggingExecutors.newScheduledThreadPool("Geode Failure Detection Scheduler", 1);
+    checkExecutor = LoggingExecutors.newCachedThreadPool("Geode Failure Detection thread ", true);
     Monitor m = this.new Monitor(memberTimeout);
     long delay = memberTimeout / LOGICAL_INTERVAL;
     monitorFuture = scheduler.scheduleAtFixedRate(m, delay, delay, TimeUnit.MILLISECONDS);
-
-    serverSocketExecutor = Executors.newCachedThreadPool(new ThreadFactory() {
-      final AtomicInteger threadIdx = new AtomicInteger();
-
-      @Override
-      public Thread newThread(Runnable r) {
-        int id = threadIdx.getAndIncrement();
-        Thread th =
-            new Thread(Services.getThreadGroup(), r, "Geode Failure Detection Server thread " + id);
-        th.setDaemon(true);
-        return th;
-      }
-    });
-
+    serverSocketExecutor =
+        LoggingExecutors.newCachedThreadPool("Geode Failure Detection Server thread ", true);
   }
 
   ServerSocket createServerSocket(InetAddress socketAddress, int[] portRange) {
diff --git a/geode-core/src/main/java/org/apache/geode/distributed/internal/membership/gms/membership/GMSJoinLeave.java b/geode-core/src/main/java/org/apache/geode/distributed/internal/membership/gms/membership/GMSJoinLeave.java
index cdb8fae..6901e04 100644
--- a/geode-core/src/main/java/org/apache/geode/distributed/internal/membership/gms/membership/GMSJoinLeave.java
+++ b/geode-core/src/main/java/org/apache/geode/distributed/internal/membership/gms/membership/GMSJoinLeave.java
@@ -44,10 +44,7 @@ import java.util.Set;
 import java.util.TimerTask;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.commons.lang.StringUtils;
 import org.apache.logging.log4j.Logger;
@@ -82,6 +79,8 @@ import org.apache.geode.distributed.internal.membership.gms.messages.ViewAckMess
 import org.apache.geode.distributed.internal.tcpserver.TcpClient;
 import org.apache.geode.internal.Version;
 import org.apache.geode.internal.i18n.LocalizedStrings;
+import org.apache.geode.internal.logging.LoggingExecutors;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.security.AuthenticationRequiredException;
 import org.apache.geode.security.GemFireSecurityException;
 
@@ -815,12 +814,11 @@ public class GMSJoinLeave implements JoinLeave, MessageHandler {
   private void createAndStartViewCreator(NetView newView) {
     if (viewCreator == null || viewCreator.isShutdown()) {
       services.getMessenger().initClusterKey();
-      viewCreator = new ViewCreator("Geode Membership View Creator", Services.getThreadGroup());
+      viewCreator = new ViewCreator("Geode Membership View Creator");
       if (newView != null) {
         viewCreator.setInitialView(newView, newView.getNewMembers(), newView.getShutdownMembers(),
             newView.getCrashedMembers());
       }
-      viewCreator.setDaemon(true);
       logger.info("ViewCreator starting on:" + localAddress);
       viewCreator.start();
     }
@@ -2045,7 +2043,7 @@ public class GMSJoinLeave implements JoinLeave, MessageHandler {
     }
   }
 
-  class ViewCreator extends Thread {
+  class ViewCreator extends LoggingThread {
     volatile boolean shutdown = false;
     volatile boolean waiting = false;
     volatile boolean testFlagForRemovalRequest = false;
@@ -2071,8 +2069,8 @@ public class GMSJoinLeave implements JoinLeave, MessageHandler {
      */
     Set<InternalDistributedMember> initialRemovals;
 
-    ViewCreator(String name, ThreadGroup tg) {
-      super(tg, name);
+    ViewCreator(String name) {
+      super(name);
     }
 
     void shutdown() {
@@ -2692,16 +2690,9 @@ public class GMSJoinLeave implements JoinLeave, MessageHandler {
       }
 
       logger.debug("checking availability of these members: {}", checkers);
-      ExecutorService svc = Executors.newFixedThreadPool(suspects.size(), new ThreadFactory() {
-        AtomicInteger i = new AtomicInteger();
-
-        @Override
-        public Thread newThread(Runnable r) {
-          return new Thread(Services.getThreadGroup(), r,
-              "Geode View Creator verification thread " + i.incrementAndGet());
-        }
-      });
-
+      ExecutorService svc =
+          LoggingExecutors.newFixedThreadPool("Geode View Creator verification thread ",
+              false, suspects.size());
       try {
         long giveUpTime = System.currentTimeMillis() + viewAckTimeout;
         // submit the tasks that will remove dead members from the suspects collection
diff --git a/geode-core/src/main/java/org/apache/geode/distributed/internal/membership/gms/mgr/GMSMembershipManager.java b/geode-core/src/main/java/org/apache/geode/distributed/internal/membership/gms/mgr/GMSMembershipManager.java
index cb19969..cf9f4c0 100644
--- a/geode-core/src/main/java/org/apache/geode/distributed/internal/membership/gms/mgr/GMSMembershipManager.java
+++ b/geode-core/src/main/java/org/apache/geode/distributed/internal/membership/gms/mgr/GMSMembershipManager.java
@@ -87,6 +87,7 @@ import org.apache.geode.internal.cache.partitioned.PartitionMessageWithDirectRep
 import org.apache.geode.internal.cache.xmlcache.CacheServerCreation;
 import org.apache.geode.internal.cache.xmlcache.CacheXmlGenerator;
 import org.apache.geode.internal.i18n.LocalizedStrings;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.AlertAppender;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.logging.log4j.LogMarker;
@@ -902,23 +903,19 @@ public class GMSMembershipManager implements MembershipManager, Manager {
       if (latestView.getViewId() > member.getVmViewId()) {
         // tell the process that it should shut down distribution.
         // Run in a separate thread so we don't hold the view lock during the request. Bug #44995
-        new Thread(Thread.currentThread().getThreadGroup(),
-            "Removing shunned GemFire node " + member) {
-          @Override
-          public void run() {
-            // fix for bug #42548
-            // this is an old member that shouldn't be added
-            logger.warn(LocalizedMessage.create(
-                LocalizedStrings.GroupMembershipService_Invalid_Surprise_Member,
-                new Object[] {member, latestView}));
-            try {
-              requestMemberRemoval(member,
-                  "this member is no longer in the view but is initiating connections");
-            } catch (CancelException e) {
-              // okay to ignore
-            }
+        new LoggingThread("Removing shunned GemFire node " + member, false, () -> {
+          // fix for bug #42548
+          // this is an old member that shouldn't be added
+          logger.warn(LocalizedMessage.create(
+              LocalizedStrings.GroupMembershipService_Invalid_Surprise_Member,
+              new Object[] {member, latestView}));
+          try {
+            requestMemberRemoval(member,
+                "this member is no longer in the view but is initiating connections");
+          } catch (CancelException e) {
+            // okay to ignore
           }
-        }.start();
+        }).start();
         addShunnedMember(member);
         return false;
       }
@@ -1971,35 +1968,24 @@ public class GMSMembershipManager implements MembershipManager, Manager {
 
     final DirectChannel dc = directChannel;
     if (dc != null) {
-      // if (crashed) {
-      // dc.closeEndpoint(member, reason);
-      // }
-      // else
       // Bug 37944: make sure this is always done in a separate thread,
       // so that shutdown conditions don't wedge the view lock
-      { // fix for bug 34010
-        Thread t = new Thread() {
-          @Override
-          public void run() {
-            try {
-              Thread.sleep(Integer.getInteger("p2p.disconnectDelay", 3000).intValue());
-            } catch (InterruptedException ie) {
-              Thread.currentThread().interrupt();
-              // Keep going, try to close the endpoint.
-            }
-            if (!dc.isOpen()) {
-              return;
-            }
-            if (logger.isDebugEnabled())
-              logger.debug("Membership: closing connections for departed member {}", member);
-            // close connections, but don't do membership notification since it's already been done
-            dc.closeEndpoint(member, reason, false);
-          }
-        };
-        t.setDaemon(true);
-        t.setName("disconnect thread for " + member);
-        t.start();
-      } // fix for bug 34010
+      // fix for bug 34010
+      new LoggingThread("disconnect thread for " + member, () -> {
+        try {
+          Thread.sleep(Integer.getInteger("p2p.disconnectDelay", 3000).intValue());
+        } catch (InterruptedException ie) {
+          Thread.currentThread().interrupt();
+          // Keep going, try to close the endpoint.
+        }
+        if (!dc.isOpen()) {
+          return;
+        }
+        if (logger.isDebugEnabled())
+          logger.debug("Membership: closing connections for departed member {}", member);
+        // close connections, but don't do membership notification since it's already been done
+        dc.closeEndpoint(member, reason, false);
+      }).start();
     }
   }
 
@@ -2551,7 +2537,7 @@ public class GMSMembershipManager implements MembershipManager, Manager {
     }
 
 
-    Thread reconnectThread = new Thread(() -> {
+    Thread reconnectThread = new LoggingThread("DisconnectThread", false, () -> {
       // stop server locators immediately since they may not have correct
       // information. This has caused client failures in bridge/wan
       // network-down testing
@@ -2560,12 +2546,8 @@ public class GMSMembershipManager implements MembershipManager, Manager {
         loc.stop(true, !services.getConfig().getDistributionConfig().getDisableAutoReconnect(),
             false);
       }
-
       uncleanShutdown(reason, shutdownCause);
     });
-
-    reconnectThread.setName("DisconnectThread");
-    reconnectThread.setDaemon(false);
     reconnectThread.start();
   }
 
diff --git a/geode-core/src/main/java/org/apache/geode/distributed/internal/tcpserver/TcpServer.java b/geode-core/src/main/java/org/apache/geode/distributed/internal/tcpserver/TcpServer.java
index eaffc03..761bb3b 100755
--- a/geode-core/src/main/java/org/apache/geode/distributed/internal/tcpserver/TcpServer.java
+++ b/geode-core/src/main/java/org/apache/geode/distributed/internal/tcpserver/TcpServer.java
@@ -30,11 +30,9 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
-import java.util.concurrent.SynchronousQueue;
-import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.net.ssl.SSLException;
 
@@ -52,7 +50,6 @@ import org.apache.geode.distributed.internal.InternalConfigurationPersistenceSer
 import org.apache.geode.distributed.internal.InternalDistributedSystem;
 import org.apache.geode.distributed.internal.InternalLocator;
 import org.apache.geode.distributed.internal.PoolStatHelper;
-import org.apache.geode.distributed.internal.PooledExecutorWithDMStats;
 import org.apache.geode.internal.DSFIDFactory;
 import org.apache.geode.internal.GemFireVersion;
 import org.apache.geode.internal.Version;
@@ -67,6 +64,8 @@ import org.apache.geode.internal.cache.client.protocol.exception.ServiceVersionN
 import org.apache.geode.internal.cache.tier.CommunicationMode;
 import org.apache.geode.internal.cache.tier.sockets.Handshake;
 import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.logging.LoggingExecutors;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.net.SocketCreator;
 import org.apache.geode.internal.net.SocketCreatorFactory;
 import org.apache.geode.internal.security.SecurableCommunicationChannel;
@@ -117,7 +116,8 @@ public class TcpServer {
 
   private static final Logger log = LogService.getLogger();
 
-  protected static final int READ_TIMEOUT =
+  // no longer static so that tests can test this system property
+  private final int READ_TIMEOUT =
       Integer.getInteger(DistributionConfig.GEMFIRE_PREFIX + "TcpServer.READ_TIMEOUT", 60 * 1000);
   // This is for backwards compatibility. The p2p.backlog flag used to be the only way to configure
   // the locator backlog.
@@ -135,8 +135,7 @@ public class TcpServer {
   private final ClientProtocolServiceLoader clientProtocolServiceLoader;
 
 
-  private PooledExecutorWithDMStats executor;
-  private final ThreadGroup threadGroup;
+  private ExecutorService executor;
   private final String threadName;
   private volatile Thread serverThread;
 
@@ -154,7 +153,7 @@ public class TcpServer {
 
   public TcpServer(int port, InetAddress bind_address, Properties sslConfig,
       DistributionConfigImpl cfg, TcpHandler handler, PoolStatHelper poolHelper,
-      ThreadGroup threadGroup, String threadName, InternalLocator internalLocator,
+      String threadName, InternalLocator internalLocator,
       ClientProtocolServiceLoader clientProtocolServiceLoader) {
     this.port = port;
     this.bind_address = bind_address;
@@ -167,8 +166,7 @@ public class TcpServer {
     // "precious" thread
     DSFIDFactory.registerTypes();
 
-    this.executor = createExecutor(poolHelper, threadGroup);
-    this.threadGroup = threadGroup;
+    this.executor = createExecutor(poolHelper);
     this.threadName = threadName;
 
     if (cfg == null) {
@@ -187,21 +185,9 @@ public class TcpServer {
     return socketCreator;
   }
 
-  private static PooledExecutorWithDMStats createExecutor(PoolStatHelper poolHelper,
-      final ThreadGroup threadGroup) {
-    ThreadFactory factory = new ThreadFactory() {
-      private final AtomicInteger threadNum = new AtomicInteger();
-
-      public Thread newThread(Runnable r) {
-        Thread thread = new Thread(threadGroup, r,
-            "locator request thread[" + threadNum.incrementAndGet() + "]");
-        thread.setDaemon(true);
-        return thread;
-      }
-    };
-
-    return new PooledExecutorWithDMStats(new SynchronousQueue(), MAX_POOL_SIZE, poolHelper,
-        factory, POOL_IDLE_TIMEOUT, new ThreadPoolExecutor.CallerRunsPolicy(), null);
+  private static ExecutorService createExecutor(PoolStatHelper poolHelper) {
+    return LoggingExecutors.newThreadPoolWithSynchronousFeed("locator request thread ",
+        MAX_POOL_SIZE, poolHelper, POOL_IDLE_TIMEOUT, new ThreadPoolExecutor.CallerRunsPolicy());
   }
 
   public void restarting(InternalDistributedSystem ds, InternalCache cache,
@@ -209,7 +195,7 @@ public class TcpServer {
     this.shuttingDown = false;
     this.handler.restarting(ds, cache, sharedConfig);
     startServerThread();
-    this.executor = createExecutor(this.poolHelper, this.threadGroup);
+    this.executor = createExecutor(this.poolHelper);
     log.info("TcpServer@" + System.identityHashCode(this)
         + " restarting: completed.  Server thread=" + this.serverThread + '@'
         + System.identityHashCode(this.serverThread) + ";alive=" + this.serverThread.isAlive());
@@ -224,13 +210,7 @@ public class TcpServer {
   private void startServerThread() throws IOException {
     initializeServerSocket();
     if (serverThread == null || !serverThread.isAlive()) {
-      serverThread = new Thread(threadGroup, threadName) {
-        @Override
-        public void run() {
-          TcpServer.this.run();
-        }
-      };
-      serverThread.setDaemon(true);
+      serverThread = new LoggingThread(threadName, this::run);
       serverThread.start();
     }
   }
diff --git a/geode-core/src/main/java/org/apache/geode/internal/DistributionLocator.java b/geode-core/src/main/java/org/apache/geode/internal/DistributionLocator.java
index 70a12ac..cf41823 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/DistributionLocator.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/DistributionLocator.java
@@ -29,6 +29,7 @@ import org.apache.geode.distributed.internal.InternalLocator;
 import org.apache.geode.distributed.internal.tcpserver.TcpClient;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 
 /**
@@ -151,15 +152,14 @@ public class DistributionLocator {
 
       if (!Boolean.getBoolean(InternalDistributedSystem.DISABLE_SHUTDOWN_HOOK_PROPERTY)) {
         final InetAddress faddress = address;
-        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
-          public void run() {
-            try {
-              DistributionLocator.shutdown(port, faddress);
-            } catch (IOException e) {
-              e.printStackTrace();
-            }
-          }
-        }));
+        Runtime.getRuntime()
+            .addShutdownHook(new LoggingThread("LocatorShutdownThread", false, () -> {
+              try {
+                DistributionLocator.shutdown(port, faddress);
+              } catch (IOException e) {
+                e.printStackTrace();
+              }
+            }));
       }
 
       lockFile = ManagerInfo.setLocatorStarting(directory, port, address);
diff --git a/geode-core/src/main/java/org/apache/geode/internal/OSProcess.java b/geode-core/src/main/java/org/apache/geode/internal/OSProcess.java
index 70a0afe..0c60072 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/OSProcess.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/OSProcess.java
@@ -43,7 +43,7 @@ import org.apache.geode.distributed.internal.DistributionConfig;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.io.TeePrintStream;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 
 /**
  * Used to interact with operating system processes. Use <code>exec</code> to create a new process
@@ -667,56 +667,51 @@ public class OSProcess {
     } else {
       if (reapPid(-1)) {
         pids = Collections.synchronizedSet(new HashSet());
-        ThreadGroup group = LoggingThreadGroup
-            .createThreadGroup(LocalizedStrings.OSProcess_REAPER_THREAD.toLocalizedString());
-        reaperThread = new Thread(group, new Runnable() {
-          public void run() {
-            synchronized (myPid) {
-              myPid[0] = getProcessId();
-              reaperStarted = true;
-            }
-            String trace = System.getProperty("org.apache.geode.internal.OSProcess.trace");
-            int secondsToSleep = (1000 * 60) * 1; // one minute
-            if (trace != null && trace.length() > 0) {
-              secondsToSleep = 1000; // every second
-            }
-            // reap all the pids we have every once in a while
-            while (true) {
-              SystemFailure.checkFailure();
-              try {
-                Iterator it = pids.iterator();
-                while (it.hasNext()) {
-                  Object o = it.next();
-                  int pid = ((Integer) o).intValue();
-                  if (reapPid(pid)) {
-                    try {
-                      it.remove();
-                      if (trace != null && trace.length() > 0) {
-                        System.out.println("reaped pid: " + pid);
-                      }
-                    } catch (Exception e) {
-                      // make sure and remove it since it was
-                      // reaped.
-                      pids.remove(o);
-                      if (trace != null && trace.length() > 0) {
-                        System.out.println("reaped pid: " + pid);
-                      }
-                      throw e;
+        reaperThread = new LoggingThread("osprocess reaper", () -> {
+          synchronized (myPid) {
+            myPid[0] = getProcessId();
+            reaperStarted = true;
+          }
+          String trace = System.getProperty("org.apache.geode.internal.OSProcess.trace");
+          int secondsToSleep = (1000 * 60) * 1; // one minute
+          if (trace != null && trace.length() > 0) {
+            secondsToSleep = 1000; // every second
+          }
+          // reap all the pids we have every once in a while
+          while (true) {
+            SystemFailure.checkFailure();
+            try {
+              Iterator it = pids.iterator();
+              while (it.hasNext()) {
+                Object o = it.next();
+                int pid = ((Integer) o).intValue();
+                if (reapPid(pid)) {
+                  try {
+                    it.remove();
+                    if (trace != null && trace.length() > 0) {
+                      System.out.println("reaped pid: " + pid);
+                    }
+                  } catch (Exception e) {
+                    // make sure and remove it since it was
+                    // reaped.
+                    pids.remove(o);
+                    if (trace != null && trace.length() > 0) {
+                      System.out.println("reaped pid: " + pid);
                     }
+                    throw e;
                   }
                 }
-                Thread.sleep(secondsToSleep);
-              } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-                break;
-              } catch (Exception e) {
-                // e.printStackTrace(); // DEBUG
-                // ignore
               }
+              Thread.sleep(secondsToSleep);
+            } catch (InterruptedException e) {
+              Thread.currentThread().interrupt();
+              break;
+            } catch (Exception e) {
+              // e.printStackTrace(); // DEBUG
+              // ignore
             }
           }
-        }, "osprocess reaper");
-        reaperThread.setDaemon(true);
+        });
         reaperThread.start();
       } else {
         // platform does not need a reaper thread,
diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/remote/RemoteGemFireVM.java b/geode-core/src/main/java/org/apache/geode/internal/admin/remote/RemoteGemFireVM.java
index 8e55f10..ec7dbb6 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/admin/remote/RemoteGemFireVM.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/admin/remote/RemoteGemFireVM.java
@@ -48,6 +48,7 @@ import org.apache.geode.internal.admin.StatAlertDefinition;
 import org.apache.geode.internal.admin.StatListener;
 import org.apache.geode.internal.admin.StatResource;
 import org.apache.geode.internal.i18n.LocalizedStrings;
+import org.apache.geode.internal.logging.LoggingThread;
 
 /**
  * Provides access to a remote gemfire VM for purposes of gathering statistics and other info
@@ -793,13 +794,12 @@ public abstract class RemoteGemFireVM implements GemFireVM {
    * off of a queue and delivers callbacks to the appropriate
    * {@link org.apache.geode.internal.admin.StatListener}.
    */
-  private class StatDispatcher extends Thread {
+  private class StatDispatcher extends LoggingThread {
     private BlockingQueue queue = new LinkedBlockingQueue();
     private volatile boolean stopped = false;
 
     protected StatDispatcher() {
-      super(RemoteGemFireVM.this.agent.getThreadGroup(), "StatDispatcher");
-      setDaemon(true);
+      super("StatDispatcher");
     }
 
     protected synchronized void stopDispatching() {
diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/remote/RemoteGfManagerAgent.java b/geode-core/src/main/java/org/apache/geode/internal/admin/remote/RemoteGfManagerAgent.java
index d91a500..c07203b 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/admin/remote/RemoteGfManagerAgent.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/admin/remote/RemoteGfManagerAgent.java
@@ -63,7 +63,7 @@ import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.InternalLogWriter;
 import org.apache.geode.internal.logging.LogService;
 import org.apache.geode.internal.logging.LogWriterFactory;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.logging.log4j.LogMarker;
 import org.apache.geode.security.AuthenticationFailedException;
@@ -158,9 +158,6 @@ class RemoteGfManagerAgent implements GfManagerAgent {
    */
   protected volatile boolean abortCurrentJoin = false;
 
-  /** A thread group for threads created by this manager agent */
-  protected ThreadGroup threadGroup;
-
   /**
    * Has this <code>RemoteGfManagerAgent</code> been initialized? That is, after it has been
    * connected has this agent created admin objects for the initial members of the distributed
@@ -281,7 +278,6 @@ class RemoteGfManagerAgent implements GfManagerAgent {
 
     this.disconnectListener = cfg.getDisconnectListener();
 
-    this.threadGroup = LoggingThreadGroup.createThreadGroup("ConsoleDMDaemon", logger);
     this.joinProcessor = new JoinProcessor();
     this.joinProcessor.start();
 
@@ -902,16 +898,6 @@ class RemoteGfManagerAgent implements GfManagerAgent {
     } // sync
   }
 
-  /**
-   * Returns the thread group in which admin threads should run. This thread group handles uncaught
-   * exceptions nicely.
-   *
-   * @since GemFire 4.0
-   */
-  public ThreadGroup getThreadGroup() {
-    return this.threadGroup;
-  }
-
   //////////// inner classes ///////////////////////////
 
   /**
@@ -920,13 +906,12 @@ class RemoteGfManagerAgent implements GfManagerAgent {
    * members or attempts to connect to a distributed system whose member run a different version of
    * GemFire.
    */
-  private class DSConnectionDaemon extends Thread {
+  private class DSConnectionDaemon extends LoggingThread {
     /** Has this thread been told to stop? */
     private volatile boolean shutDown = false;
 
     protected DSConnectionDaemon() {
-      super(RemoteGfManagerAgent.this.threadGroup, "DSConnectionDaemon");
-      setDaemon(true);
+      super("DSConnectionDaemon");
     }
 
     public void shutDown() {
@@ -995,12 +980,11 @@ class RemoteGfManagerAgent implements GfManagerAgent {
    * A daemon thread that reads {@link SnapshotResultMessage}s from a queue and invokes the
    * <code>CacheCollector</code> accordingly.
    */
-  private class SnapshotResultDispatcher extends Thread {
+  private class SnapshotResultDispatcher extends LoggingThread {
     private volatile boolean shutDown = false;
 
     public SnapshotResultDispatcher() {
-      super(RemoteGfManagerAgent.this.threadGroup, "SnapshotResultDispatcher");
-      setDaemon(true);
+      super("SnapshotResultDispatcher");
     }
 
     public void shutDown() {
@@ -1188,15 +1172,14 @@ class RemoteGfManagerAgent implements GfManagerAgent {
    * thread as the membership handler, then we run the risk of getting deadlocks and such.
    */
   // FIXME: Revisit/redesign this code
-  private class JoinProcessor extends Thread {
+  private class JoinProcessor extends LoggingThread {
     private volatile boolean paused = false;
     private volatile boolean shutDown = false;
     private volatile InternalDistributedMember id;
     private final Object lock = new Object();
 
     public JoinProcessor() {
-      super(RemoteGfManagerAgent.this.threadGroup, "JoinProcessor");
-      setDaemon(true);
+      super("JoinProcessor");
     }
 
     public void shutDown() {
diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/remote/ShutdownAllRequest.java b/geode-core/src/main/java/org/apache/geode/internal/admin/remote/ShutdownAllRequest.java
index 1d074fc..3d73509 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/admin/remote/ShutdownAllRequest.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/admin/remote/ShutdownAllRequest.java
@@ -41,6 +41,7 @@ import org.apache.geode.distributed.internal.ReplyMessage;
 import org.apache.geode.distributed.internal.membership.InternalDistributedMember;
 import org.apache.geode.internal.cache.InternalCache;
 import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.tcp.ConnectionTable;
 
 /**
@@ -151,17 +152,14 @@ public class ShutdownAllRequest extends AdminRequest {
       // and causes a 20 second delay.
       final InternalDistributedSystem ids = dm.getSystem();
       if (ids.isConnected()) {
-        Thread t = new Thread(new Runnable() {
-          @Override
-          public void run() {
-            try {
-              Thread.sleep(SLEEP_TIME_BEFORE_DISCONNECT_DS);
-            } catch (InterruptedException ignore) {
-            }
-            ConnectionTable.threadWantsSharedResources();
-            if (ids.isConnected()) {
-              ids.disconnect();
-            }
+        Thread t = new LoggingThread("ShutdownAllRequestDisconnectThread", false, () -> {
+          try {
+            Thread.sleep(SLEEP_TIME_BEFORE_DISCONNECT_DS);
+          } catch (InterruptedException ignore) {
+          }
+          ConnectionTable.threadWantsSharedResources();
+          if (ids.isConnected()) {
+            ids.disconnect();
           }
         });
         t.start();
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/ColocationLogger.java b/geode-core/src/main/java/org/apache/geode/internal/cache/ColocationLogger.java
index 7cf761e..2b029c1 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/ColocationLogger.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/ColocationLogger.java
@@ -25,6 +25,7 @@ import org.apache.geode.SystemFailure;
 import org.apache.geode.distributed.DistributedSystem;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 
 /**
@@ -51,7 +52,7 @@ public class ColocationLogger implements Runnable {
    */
   public ColocationLogger(PartitionedRegion region) {
     this.region = region;
-    loggerThread = new Thread(this, "ColocationLogger for " + region.getName());
+    loggerThread = new LoggingThread("ColocationLogger for " + region.getName(), false, this);
     loggerThread.start();
   }
 
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/DiskStoreImpl.java b/geode-core/src/main/java/org/apache/geode/internal/cache/DiskStoreImpl.java
index d174157..b94acf5 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/DiskStoreImpl.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/DiskStoreImpl.java
@@ -47,11 +47,9 @@ import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
-import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
@@ -115,7 +113,8 @@ import org.apache.geode.internal.cache.versions.VersionTag;
 import org.apache.geode.internal.concurrent.ConcurrentHashSet;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.util.BlobHelper;
 import org.apache.geode.pdx.internal.EnumInfo;
@@ -357,9 +356,9 @@ public class DiskStoreImpl implements DiskStore {
 
   private final CountDownLatch _testHandleDiskAccessException = new CountDownLatch(1);
 
-  private final ThreadPoolExecutor diskStoreTaskPool;
+  private final ExecutorService diskStoreTaskPool;
 
-  private final ThreadPoolExecutor delayedWritePool;
+  private final ExecutorService delayedWritePool;
 
   private volatile Future lastDelayedWrite;
 
@@ -483,25 +482,10 @@ public class DiskStoreImpl implements DiskStore {
       this.oplogCompactor = null;
     }
 
-    int MAXT = DiskStoreImpl.MAX_CONCURRENT_COMPACTIONS;
-    final ThreadGroup compactThreadGroup =
-        LoggingThreadGroup.createThreadGroup("Oplog Compactor Thread Group", logger);
-    final ThreadFactory compactThreadFactory =
-        GemfireCacheHelper.CreateThreadFactory(compactThreadGroup, "Idle OplogCompactor");
-    this.diskStoreTaskPool = new ThreadPoolExecutor(MAXT, MAXT, 10, TimeUnit.SECONDS,
-        new LinkedBlockingQueue(), compactThreadFactory);
-    this.diskStoreTaskPool.allowCoreThreadTimeOut(true);
-
-
-    final ThreadGroup deleteThreadGroup =
-        LoggingThreadGroup.createThreadGroup("Oplog Delete Thread Group", logger);
-
-    final ThreadFactory deleteThreadFactory =
-        GemfireCacheHelper.CreateThreadFactory(deleteThreadGroup, "Oplog Delete Task");
-    this.delayedWritePool = new ThreadPoolExecutor(1, 1, 10, TimeUnit.SECONDS,
-        new LinkedBlockingQueue(MAX_PENDING_TASKS), deleteThreadFactory,
-        new ThreadPoolExecutor.CallerRunsPolicy());
-    this.delayedWritePool.allowCoreThreadTimeOut(true);
+    this.diskStoreTaskPool = LoggingExecutors.newFixedThreadPoolWithFeedSize("Idle OplogCompactor",
+        MAX_CONCURRENT_COMPACTIONS, Integer.MAX_VALUE);
+    this.delayedWritePool =
+        LoggingExecutors.newFixedThreadPoolWithFeedSize("Oplog Delete Task", 1, MAX_PENDING_TASKS);
   }
 
   // //////////////////// Instance Methods //////////////////////
@@ -1461,11 +1445,7 @@ public class DiskStoreImpl implements DiskStore {
   private void startAsyncFlusher() {
     final String thName =
         LocalizedStrings.DiskRegion_ASYNCHRONOUS_DISK_WRITER_0.toLocalizedString(getName());
-    this.flusherThread = new Thread(
-        LoggingThreadGroup.createThreadGroup(
-            LocalizedStrings.DiskRegion_DISK_WRITERS.toLocalizedString(), logger),
-        new FlusherThread(this), thName);
-    this.flusherThread.setDaemon(true);
+    this.flusherThread = new LoggingThread(thName, new FlusherThread(this));
     this.flusherThread.start();
   }
 
@@ -3346,22 +3326,17 @@ public class DiskStoreImpl implements DiskStore {
         LocalizedStrings.LocalRegion_A_DISKACCESSEXCEPTION_HAS_OCCURRED_WHILE_WRITING_TO_THE_DISK_FOR_DISKSTORE_0_THE_CACHE_WILL_BE_CLOSED;
     logger.error(LocalizedMessage.create(sid, DiskStoreImpl.this.getName()), dae);
 
-    final ThreadGroup exceptionHandlingGroup =
-        LoggingThreadGroup.createThreadGroup("Disk Store Exception Handling Group", logger);
-
-    Thread thread = new Thread(exceptionHandlingGroup, "Disk store exception handler") {
-      public void run() {
-        try {
-          // now close the cache
-          getCache().close(sid.toLocalizedString(DiskStoreImpl.this.getName(), dae), dae);
-          _testHandleDiskAccessException.countDown();
+    Thread thread = new LoggingThread("Disk store exception handler", false, () -> {
+      try {
+        // now close the cache
+        getCache().close(sid.toLocalizedString(DiskStoreImpl.this.getName(), dae), dae);
+        _testHandleDiskAccessException.countDown();
 
-        } catch (Exception e) {
-          logger.error(LocalizedMessage.create(
-              LocalizedStrings.LocalRegion_AN_EXCEPTION_OCCURRED_WHILE_CLOSING_THE_CACHE), e);
-        }
+      } catch (Exception e) {
+        logger.error(LocalizedMessage.create(
+            LocalizedStrings.LocalRegion_AN_EXCEPTION_OCCURRED_WHILE_CLOSING_THE_CACHE), e);
       }
-    };
+    });
     thread.start();
   }
 
@@ -4368,7 +4343,7 @@ public class DiskStoreImpl implements DiskStore {
    * tasks may take a while.
    */
   public boolean executeDiskStoreTask(final Runnable runnable) {
-    return executeDiskStoreAsyncTask(runnable, this.diskStoreTaskPool);
+    return executeAsyncTask(runnable, this.diskStoreTaskPool);
   }
 
   /**
@@ -4378,7 +4353,7 @@ public class DiskStoreImpl implements DiskStore {
    * close, etc.
    */
   public boolean executeDelayedExpensiveWrite(Runnable task) {
-    Future<?> f = executeDiskStoreTask(task, this.delayedWritePool);
+    Future<?> f = executeTask(task, this.delayedWritePool);
     lastDelayedWrite = f;
     return f != null;
   }
@@ -4400,7 +4375,7 @@ public class DiskStoreImpl implements DiskStore {
     }
   }
 
-  private Future<?> executeDiskStoreTask(final Runnable runnable, ThreadPoolExecutor executor) {
+  private Future<?> executeTask(final Runnable runnable, ExecutorService executor) {
     // schedule another thread to do it
     incBackgroundTasks();
     Future<?> result = executeDiskStoreTask(new DiskStoreTask() {
@@ -4426,7 +4401,7 @@ public class DiskStoreImpl implements DiskStore {
     return result;
   }
 
-  private boolean executeDiskStoreAsyncTask(final Runnable runnable, ThreadPoolExecutor executor) {
+  private boolean executeAsyncTask(final Runnable runnable, ExecutorService executor) {
     // schedule another thread to do it
     incBackgroundTasks();
     boolean isTaskAccepted = executeDiskStoreAsyncTask(new DiskStoreTask() {
@@ -4452,7 +4427,7 @@ public class DiskStoreImpl implements DiskStore {
     return isTaskAccepted;
   }
 
-  private Future<?> executeDiskStoreTask(DiskStoreTask r, ThreadPoolExecutor executor) {
+  private Future<?> executeDiskStoreTask(DiskStoreTask r, ExecutorService executor) {
     try {
       return executor.submit(r);
     } catch (RejectedExecutionException ex) {
@@ -4463,7 +4438,7 @@ public class DiskStoreImpl implements DiskStore {
     return null;
   }
 
-  private boolean executeDiskStoreAsyncTask(DiskStoreTask r, ThreadPoolExecutor executor) {
+  private boolean executeDiskStoreAsyncTask(DiskStoreTask r, ExecutorService executor) {
     try {
       executor.execute(r);
       return true;
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/DiskStoreMonitor.java b/geode-core/src/main/java/org/apache/geode/internal/cache/DiskStoreMonitor.java
index 0ce9d0c..0d536a0 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/DiskStoreMonitor.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/DiskStoreMonitor.java
@@ -21,9 +21,7 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.logging.log4j.Logger;
@@ -32,7 +30,7 @@ import org.apache.geode.cache.DiskAccessException;
 import org.apache.geode.distributed.internal.DistributionConfig;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.logging.log4j.LogMarker;
 
@@ -118,26 +116,13 @@ public class DiskStoreMonitor {
     if (disableMonitor) {
       exec = null;
     } else {
-      final ThreadGroup tg = LoggingThreadGroup.createThreadGroup(
-          LocalizedStrings.DiskStoreMonitor_ThreadGroup.toLocalizedString(), logger);
-      exec = Executors.newScheduledThreadPool(1, new ThreadFactory() {
-        @Override
-        public Thread newThread(Runnable r) {
-          Thread t = new Thread(tg, r, "DiskStoreMonitor");
-          t.setDaemon(true);
-          return t;
-        }
-      });
-
+      exec = LoggingExecutors.newScheduledThreadPool("DiskStoreMonitor", 1);
       // always monitor the log dir, even if there are no disk stores
-      exec.scheduleWithFixedDelay(new Runnable() {
-        @Override
-        public void run() {
-          try {
-            checkUsage();
-          } catch (Exception e) {
-            logger.error(LocalizedMessage.create(LocalizedStrings.DiskStoreMonitor_ERR), e);
-          }
+      exec.scheduleWithFixedDelay(() -> {
+        try {
+          checkUsage();
+        } catch (Exception e) {
+          logger.error(LocalizedMessage.create(LocalizedStrings.DiskStoreMonitor_ERR), e);
         }
       }, 0, USAGE_CHECK_INTERVAL, TimeUnit.MILLISECONDS);
     }
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/DistributedRegion.java b/geode-core/src/main/java/org/apache/geode/internal/cache/DistributedRegion.java
index 52d5070..fdbdccc 100755
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/DistributedRegion.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/DistributedRegion.java
@@ -121,6 +121,7 @@ import org.apache.geode.internal.cache.wan.AsyncEventQueueConfigurationException
 import org.apache.geode.internal.cache.wan.GatewaySenderConfigurationException;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.offheap.annotations.Released;
 import org.apache.geode.internal.offheap.annotations.Retained;
@@ -877,35 +878,30 @@ public class DistributedRegion extends LocalRegion implements InternalDistribute
     try {
       if (!isInitializing) {
         // moved code to a new thread.
-        Thread thread = new Thread(
-            LocalizedStrings.DistributedRegion_RECONNECT_DISTRIBUTED_SYSTEM.toLocalizedString()) {
-          @Override
-          public void run() {
-            try {
-              logger.debug(
-                  "Reliability loss with policy of reconnect and membership thread doing reconnect");
-
-              initializationLatchAfterMemberTimeout.await();
-              getSystem().tryReconnect(false, "Role Loss", getCache());
-
-              synchronized (missingRequiredRoles) {
-                // any number of threads may be waiting on missingRequiredRoles
-                missingRequiredRoles.notifyAll();
-                // need to fire an event if id is not null
-                if (hasListener() && id != null) {
-                  RoleEventImpl relEvent = new RoleEventImpl(DistributedRegion.this,
-                      Operation.CACHE_RECONNECT, null, true, id, newlyMissingRoles);
-                  dispatchListenerEvent(EnumListenerEvent.AFTER_ROLE_LOSS, relEvent);
-                }
+        Thread thread = new LoggingThread("Reconnect Distributed System", () -> {
+          try {
+            logger.debug(
+                "Reliability loss with policy of reconnect and membership thread doing reconnect");
+
+            initializationLatchAfterMemberTimeout.await();
+            getSystem().tryReconnect(false, "Role Loss", getCache());
+
+            synchronized (missingRequiredRoles) {
+              // any number of threads may be waiting on missingRequiredRoles
+              missingRequiredRoles.notifyAll();
+              // need to fire an event if id is not null
+              if (hasListener() && id != null) {
+                RoleEventImpl relEvent = new RoleEventImpl(DistributedRegion.this,
+                    Operation.CACHE_RECONNECT, null, true, id, newlyMissingRoles);
+                dispatchListenerEvent(EnumListenerEvent.AFTER_ROLE_LOSS, relEvent);
               }
-            } catch (Exception e) {
-              logger.fatal(
-                  LocalizedMessage.create(LocalizedStrings.DistributedRegion_UNEXPECTED_EXCEPTION),
-                  e);
             }
+          } catch (Exception e) {
+            logger.fatal(
+                LocalizedMessage.create(LocalizedStrings.DistributedRegion_UNEXPECTED_EXCEPTION),
+                e);
           }
-        };
-        thread.setDaemon(true);
+        });
         thread.start();
 
       } else {
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/ExpiryTask.java b/geode-core/src/main/java/org/apache/geode/internal/cache/ExpiryTask.java
index 82a02e5..e36ddbf 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/ExpiryTask.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/ExpiryTask.java
@@ -14,10 +14,8 @@
  */
 package org.apache.geode.internal.cache;
 
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.SynchronousQueue;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
 
 import org.apache.logging.log4j.Logger;
 
@@ -31,11 +29,10 @@ import org.apache.geode.cache.ExpirationAttributes;
 import org.apache.geode.cache.RegionDestroyedException;
 import org.apache.geode.distributed.internal.DistributionConfig;
 import org.apache.geode.distributed.internal.InternalDistributedSystem;
-import org.apache.geode.distributed.internal.PooledExecutorWithDMStats;
 import org.apache.geode.internal.SystemTimer;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.tcp.ConnectionTable;
 
@@ -48,41 +45,29 @@ public abstract class ExpiryTask extends SystemTimer.SystemTimerTask {
 
   private LocalRegion region; // no longer final so cancel can null it out see bug 37574
 
-  private static final ThreadPoolExecutor executor;
+  private static final ExecutorService executor;
 
   static {
     // default to inline expiry to fix bug 37115
     int nThreads = Integer.getInteger(DistributionConfig.GEMFIRE_PREFIX + "EXPIRY_THREADS", 0);
     if (nThreads > 0) {
-      ThreadFactory tf = new ThreadFactory() {
-        private int nextId = 0;
-
-        public Thread newThread(final Runnable command) {
-          String name = "Expiration threads";
-          final ThreadGroup group = LoggingThreadGroup.createThreadGroup(name);
-          final Runnable r = new Runnable() {
-            public void run() {
-              ConnectionTable.threadWantsSharedResources();
-              try {
-                command.run();
-              } finally {
-                ConnectionTable.releaseThreadsSockets();
-              }
-            }
-          };
-          Thread thread = new Thread(group, r, "Expiry " + nextId++);
-          thread.setDaemon(true);
-          return thread;
-        }
-      };
-      // LinkedBlockingQueue q = new LinkedBlockingQueue();
-      SynchronousQueue q = new SynchronousQueue();
-      executor = new PooledExecutorWithDMStats(q, nThreads, tf, null);
+      executor = LoggingExecutors.newThreadPoolWithSynchronousFeed("Expiry ",
+          (Runnable command) -> doExpiryThread(command),
+          nThreads);
     } else {
       executor = null;
     }
   }
 
+  private static void doExpiryThread(Runnable command) {
+    ConnectionTable.threadWantsSharedResources();
+    try {
+      command.run();
+    } finally {
+      ConnectionTable.releaseThreadsSockets();
+    }
+  }
+
   protected ExpiryTask(LocalRegion region) {
     this.region = region;
   }
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/GemFireCacheImpl.java b/geode-core/src/main/java/org/apache/geode/internal/cache/GemFireCacheImpl.java
index 467feee..f5a171b 100755
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/GemFireCacheImpl.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/GemFireCacheImpl.java
@@ -53,7 +53,6 @@ import java.util.ServiceLoader;
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
-import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -63,10 +62,7 @@ import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -163,7 +159,6 @@ import org.apache.geode.distributed.internal.DistributionConfig;
 import org.apache.geode.distributed.internal.DistributionManager;
 import org.apache.geode.distributed.internal.InternalDistributedSystem;
 import org.apache.geode.distributed.internal.InternalLocator;
-import org.apache.geode.distributed.internal.PooledExecutorWithDMStats;
 import org.apache.geode.distributed.internal.ReplyException;
 import org.apache.geode.distributed.internal.ReplyProcessor21;
 import org.apache.geode.distributed.internal.ResourceEvent;
@@ -215,7 +210,8 @@ import org.apache.geode.internal.jndi.JNDIInvoker;
 import org.apache.geode.internal.jta.TransactionManagerImpl;
 import org.apache.geode.internal.logging.InternalLogWriter;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.monitoring.ThreadsMonitoring;
 import org.apache.geode.internal.net.SocketCreator;
@@ -372,7 +368,7 @@ public class GemFireCacheImpl implements InternalCache, InternalClientCache, Has
   private final Date creationDate;
 
   /** thread pool for event dispatching */
-  private final ThreadPoolExecutor eventThreadPool;
+  private final ExecutorService eventThreadPool;
 
   /**
    * the list of all cache servers. CopyOnWriteArrayList is used to allow concurrent add, remove and
@@ -903,20 +899,13 @@ public class GemFireCacheImpl implements InternalCache, InternalClientCache, Has
       this.persistentMemberManager = new PersistentMemberManager();
 
       if (asyncEventListeners) {
-        final ThreadGroup threadGroup =
-            LoggingThreadGroup.createThreadGroup("Message Event Threads", logger);
-        ThreadFactory threadFactory = (Runnable command) -> {
-          final Runnable runnable = () -> {
-            ConnectionTable.threadWantsSharedResources();
-            command.run();
-          };
-          Thread thread = new Thread(threadGroup, runnable, "Message Event Thread");
-          thread.setDaemon(true);
-          return thread;
-        };
-        ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(EVENT_QUEUE_LIMIT);
-        this.eventThreadPool = new PooledExecutorWithDMStats(queue, EVENT_THREAD_LIMIT,
-            this.cachePerfStats.getEventPoolHelper(), threadFactory, 1000, getThreadMonitorObj());
+        this.eventThreadPool = LoggingExecutors.newThreadPoolWithFixedFeed("Message Event Thread",
+            command -> {
+              ConnectionTable.threadWantsSharedResources();
+              command.run();
+            }, EVENT_THREAD_LIMIT, this.cachePerfStats.getEventPoolHelper(), 1000,
+            getThreadMonitorObj(),
+            EVENT_QUEUE_LIMIT);
       } else {
         this.eventThreadPool = null;
       }
@@ -1806,21 +1795,9 @@ public class GemFireCacheImpl implements InternalCache, InternalClientCache, Has
   }
 
   private ExecutorService getShutdownAllExecutorService(int size) {
-    final ThreadGroup threadGroup =
-        LoggingThreadGroup.createThreadGroup("ShutdownAllGroup", logger);
-    ThreadFactory threadFactory = new ThreadFactory() {
-      private final AtomicInteger threadCount = new AtomicInteger(1);
-
-      @Override
-      public Thread newThread(Runnable runnable) {
-        Thread thread =
-            new Thread(threadGroup, runnable, "ShutdownAll-" + this.threadCount.getAndIncrement());
-        thread.setDaemon(true);
-        return thread;
-      }
-    };
-    return Executors.newFixedThreadPool(shutdownAllPoolSize == -1 ? size : shutdownAllPoolSize,
-        threadFactory);
+    return LoggingExecutors
+        .newFixedThreadPool("ShutdownAll-", true,
+            shutdownAllPoolSize == -1 ? size : shutdownAllPoolSize);
   }
 
   private void shutDownOnePRGracefully(PartitionedRegion partitionedRegion) {
@@ -4499,10 +4476,7 @@ public class GemFireCacheImpl implements InternalCache, InternalClientCache, Has
           }
 
           this.queryMonitor = new QueryMonitor(this, maxTime);
-          final LoggingThreadGroup group =
-              LoggingThreadGroup.createThreadGroup("QueryMonitor Thread Group", logger);
-          Thread qmThread = new Thread(group, this.queryMonitor, "QueryMonitor Thread");
-          qmThread.setDaemon(true);
+          Thread qmThread = new LoggingThread("QueryMonitor Thread", this.queryMonitor);
           qmThread.start();
           if (logger.isDebugEnabled()) {
             logger.debug("QueryMonitor thread started.");
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/GemfireCacheHelper.java b/geode-core/src/main/java/org/apache/geode/internal/cache/GemfireCacheHelper.java
deleted file mode 100755
index dfce671..0000000
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/GemfireCacheHelper.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
- * agreements. See the NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance with the License. You may obtain a
- * copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package org.apache.geode.internal.cache;
-
-import java.util.concurrent.ThreadFactory;
-
-/*
- * Purpose of this class to create threadfactory and other helper classes for GemfireCache. If we
- * keep these classes as inner class of GemFireCache then some time it holds reference of static
- * cache Which can cause leak if app is just starting and closing cache
- */
-public class GemfireCacheHelper {
-
-  public static ThreadFactory CreateThreadFactory(final ThreadGroup tg, final String threadName) {
-    final ThreadFactory threadFactory = new ThreadFactory() {
-      public Thread newThread(Runnable command) {
-        Thread thread = new Thread(tg, command, threadName);
-        thread.setDaemon(true);
-        return thread;
-      }
-    };
-    return threadFactory;
-  }
-}
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/InitialImageOperation.java b/geode-core/src/main/java/org/apache/geode/internal/cache/InitialImageOperation.java
index 1f04280..96472e0 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/InitialImageOperation.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/InitialImageOperation.java
@@ -87,7 +87,7 @@ import org.apache.geode.internal.cache.vmotion.VMotionObserverHolder;
 import org.apache.geode.internal.cache.wan.AbstractGatewaySender;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.logging.log4j.LogMarker;
 import org.apache.geode.internal.sequencelog.EntryLogger;
@@ -2052,15 +2052,9 @@ public class InitialImageOperation {
         }
         // can't disconnect the distributed system in a thread owned by the ds,
         // so start a new thread to do the work
-        ThreadGroup group =
-            LoggingThreadGroup.createThreadGroup("InitialImageOperation abortTest Threads", logger);
-        Thread disconnectThread = new Thread(group, "InitialImageOperation abortTest Thread") {
-          @Override
-          public void run() {
-            dm.getSystem().disconnect();
-          }
-        };
-        disconnectThread.setDaemon(true);
+        Thread disconnectThread =
+            new LoggingThread("InitialImageOperation abortTest Thread",
+                () -> dm.getSystem().disconnect());
         disconnectThread.start();
       } // !isDisconnecting
       // ...end of abortTest code
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/PRHARedundancyProvider.java b/geode-core/src/main/java/org/apache/geode/internal/cache/PRHARedundancyProvider.java
index 57a6d9c..5640e07 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/PRHARedundancyProvider.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/PRHARedundancyProvider.java
@@ -86,6 +86,7 @@ import org.apache.geode.internal.cache.persistence.PersistentMemberID;
 import org.apache.geode.internal.cache.persistence.PersistentStateListener;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.monitoring.ThreadsMonitoring;
 
@@ -1710,8 +1711,8 @@ public class PRHARedundancyProvider {
     try {
       if (proxyBucketArray.length > 0) {
         this.redundancyLogger = new RedundancyLogger(this);
-        Thread loggingThread = new Thread(this.redundancyLogger,
-            "RedundancyLogger for region " + this.prRegion.getName());
+        Thread loggingThread = new LoggingThread(
+            "RedundancyLogger for region " + this.prRegion.getName(), false, this.redundancyLogger);
         loggingThread.start();
       }
     } catch (RuntimeException e) {
@@ -1757,7 +1758,8 @@ public class PRHARedundancyProvider {
           }
         };
         Thread recoveryThread =
-            new Thread(recoveryRunnable, "Recovery thread for bucket " + proxyBucket.getName());
+            new LoggingThread("Recovery thread for bucket " + proxyBucket.getName(),
+                false, recoveryRunnable);
         recoveryThread.start();
         bucketsHostedLocally.add(proxyBucket);
       } else {
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/PRQueryProcessor.java b/geode-core/src/main/java/org/apache/geode/internal/cache/PRQueryProcessor.java
index ac52669..9b0bdbf 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/PRQueryProcessor.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/PRQueryProcessor.java
@@ -28,7 +28,6 @@ import java.util.List;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -59,6 +58,7 @@ import org.apache.geode.internal.cache.PartitionedRegionQueryEvaluator.PRQueryRe
 import org.apache.geode.internal.cache.execute.BucketMovedException;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.logging.LoggingExecutors;
 
 /**
  * This class takes the responsibility of executing the query on a data store for the buckets
@@ -461,7 +461,7 @@ public class PRQueryProcessor {
     static synchronized void initializeExecutorService() {
       if (execService == null || execService.isShutdown() || execService.isTerminated()) {
         int numThreads = (TEST_NUM_THREADS > 1 ? TEST_NUM_THREADS : NUM_THREADS);
-        execService = Executors.newFixedThreadPool(numThreads);
+        execService = LoggingExecutors.newFixedThreadPool("PRQueryProcessor", false, numThreads);
       }
     }
   }
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/PartitionedRegion.java b/geode-core/src/main/java/org/apache/geode/internal/cache/PartitionedRegion.java
index 86b35dc..557585e 100755
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/PartitionedRegion.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/PartitionedRegion.java
@@ -40,10 +40,8 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executors;
 import java.util.concurrent.FutureTask;
 import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -238,7 +236,7 @@ import org.apache.geode.internal.cache.wan.parallel.ParallelGatewaySenderQueue;
 import org.apache.geode.internal.concurrent.ConcurrentHashSet;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.logging.log4j.LogMarker;
 import org.apache.geode.internal.offheap.annotations.Released;
@@ -812,15 +810,7 @@ public class PartitionedRegion extends LocalRegion
     if (this.getEvictionAttributes() != null
         && this.getEvictionAttributes().getAlgorithm().isLRUHeap()) {
       this.sortedBuckets = new ArrayList<BucketRegion>();
-      final ThreadGroup grp = LoggingThreadGroup.createThreadGroup("BucketSorterThread", logger);
-      ThreadFactory tf = new ThreadFactory() {
-        public Thread newThread(Runnable r) {
-          Thread t = new Thread(grp, r, "BucketSorterThread");
-          t.setDaemon(true);
-          return t;
-        }
-      };
-      this.bucketSorter = Executors.newScheduledThreadPool(1, tf);
+      this.bucketSorter = LoggingExecutors.newScheduledThreadPool("BucketSorterThread", 1);
     }
     // If eviction is on, Create an instance of PartitionedRegionLRUStatistics
     if ((this.getEvictionAttributes() != null
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/TXCommitMessage.java b/geode-core/src/main/java/org/apache/geode/internal/cache/TXCommitMessage.java
index 6131a8a..dfd8796 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/TXCommitMessage.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/TXCommitMessage.java
@@ -69,7 +69,7 @@ import org.apache.geode.internal.cache.versions.VersionSource;
 import org.apache.geode.internal.cache.versions.VersionTag;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.offheap.annotations.Released;
 
@@ -1971,6 +1971,16 @@ public class TXCommitMessage extends PooledDistributionMessage
     return departureNoticed;
   }
 
+  private void doOriginDepartedCommit() {
+    try {
+      // Set processor to zero to avoid the ack to the now departed origin
+      processorId = 0;
+      basicProcess();
+    } finally {
+      txTracker.processed(this);
+    }
+  }
+
   @Override
   public void memberDeparted(DistributionManager distributionManager,
       final InternalDistributedMember id, boolean crashed) {
@@ -1991,8 +2001,6 @@ public class TXCommitMessage extends PooledDistributionMessage
       this.departureNoticed = true;
     }
 
-    ThreadGroup group = LoggingThreadGroup.createThreadGroup("TXCommitMessage Threads", logger);
-
     // Send message to fellow FarSiders (aka recipients), if any, to
     // determine if any one of them have received a CommitProcessMessage
     if (getFarSiders() != null && !getFarSiders().isEmpty()) {
@@ -2003,15 +2011,10 @@ public class TXCommitMessage extends PooledDistributionMessage
 
       // Create a new thread, send the CommitProcessQuery, wait for a response and potentially
       // process
-      Thread fellowFarSidersQuery = new Thread(group, "CommitProcessQuery Thread") {
-        // Should I use a thread pool?, Darrel suggests look in DM somewhere or introduce a zero
-        // sized thread pool
-        @Override
-        public void run() {
-          doCommitProcessQuery(id);
-        }
-      };
-      fellowFarSidersQuery.setDaemon(true);
+      // Should I use a thread pool?, Darrel suggests look in DM somewhere or introduce a zero
+      // sized thread pool
+      Thread fellowFarSidersQuery = new LoggingThread("CommitProcessQuery Thread",
+          () -> doCommitProcessQuery(id));
       fellowFarSidersQuery.start();
     } else {
       if (logger.isDebugEnabled()) {
@@ -2022,20 +2025,8 @@ public class TXCommitMessage extends PooledDistributionMessage
       // will never get the CommitProcess message, but it
       // doesn't matter since we can commit anyway.
       // Start a new thread to process the commit
-      Thread originDepartedCommit = new Thread(group, "Origin Departed Commit") {
-        @Override
-        public void run() {
-          final TXCommitMessage mess = TXCommitMessage.this;
-          try {
-            // Set processor to zero to avoid the ack to the now departed origin
-            mess.processorId = 0;
-            mess.basicProcess();
-          } finally {
-            txTracker.processed(mess);
-          }
-        }
-      };
-      originDepartedCommit.setDaemon(true);
+      Thread originDepartedCommit = new LoggingThread("Origin Departed Commit",
+          this::doOriginDepartedCommit);
       originDepartedCommit.start();
     }
   }
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/TombstoneService.java b/geode-core/src/main/java/org/apache/geode/internal/cache/TombstoneService.java
index 14c1d53..1746087 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/TombstoneService.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/TombstoneService.java
@@ -43,7 +43,7 @@ import org.apache.geode.internal.cache.versions.VersionSource;
 import org.apache.geode.internal.cache.versions.VersionTag;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.logging.log4j.LogMarker;
 import org.apache.geode.internal.size.ReflectionSingleObjectSizer;
@@ -787,10 +787,7 @@ public class TombstoneService {
       this.tombstones = new ConcurrentLinkedQueue<Tombstone>();
       this.memoryUsedEstimate = new AtomicLong();
       this.queueHeadLock = new StoppableReentrantLock(cancelCriterion);
-      this.sweeperThread = new Thread(
-          LoggingThreadGroup.createThreadGroup("Destroyed Entries Processors", logger), this);
-      this.sweeperThread.setDaemon(true);
-      this.sweeperThread.setName(threadName);
+      this.sweeperThread = new LoggingThread(threadName, this);
       this.lastPurgeTimestamp = getNow();
     }
 
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/backup/BackupService.java b/geode-core/src/main/java/org/apache/geode/internal/cache/backup/BackupService.java
index a6ea09b..dbc9890 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/backup/BackupService.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/backup/BackupService.java
@@ -20,10 +20,7 @@ import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.logging.log4j.Logger;
@@ -36,7 +33,7 @@ import org.apache.geode.internal.cache.DiskStoreImpl;
 import org.apache.geode.internal.cache.InternalCache;
 import org.apache.geode.internal.cache.Oplog;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
 
 public class BackupService {
   private static final Logger logger = LogService.getLogger();
@@ -136,20 +133,7 @@ public class BackupService {
   }
 
   private ExecutorService createExecutor() {
-    LoggingThreadGroup group = LoggingThreadGroup.createThreadGroup("BackupService Thread", logger);
-    ThreadFactory threadFactory = new ThreadFactory() {
-
-      private final AtomicInteger threadId = new AtomicInteger();
-
-      @Override
-      public Thread newThread(final Runnable command) {
-        Thread thread =
-            new Thread(group, command, "BackupServiceThread" + threadId.incrementAndGet());
-        thread.setDaemon(true);
-        return thread;
-      }
-    };
-    return Executors.newSingleThreadExecutor(threadFactory);
+    return LoggingExecutors.newSingleThreadExecutor("BackupServiceThread", true);
   }
 
   private void cleanup() {
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/control/HeapMemoryMonitor.java b/geode-core/src/main/java/org/apache/geode/internal/cache/control/HeapMemoryMonitor.java
index bed9e72..3996b83 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/control/HeapMemoryMonitor.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/control/HeapMemoryMonitor.java
@@ -22,10 +22,8 @@ import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.Executors;
 import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 
 import javax.management.ListenerNotFoundException;
@@ -50,7 +48,7 @@ import org.apache.geode.internal.cache.control.MemoryThresholds.MemoryState;
 import org.apache.geode.internal.cache.control.ResourceAdvisor.ResourceManagerProfile;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.statistics.GemFireStatSampler;
 import org.apache.geode.internal.statistics.LocalStatListener;
@@ -348,17 +346,7 @@ public class HeapMemoryMonitor implements NotificationListener, MemoryMonitor {
       return;
     }
 
-    final ThreadGroup threadGroup = LoggingThreadGroup.createThreadGroup("HeapPoller", logger);
-    final ThreadFactory threadFactory = new ThreadFactory() {
-      @Override
-      public Thread newThread(Runnable r) {
-        Thread thread = new Thread(threadGroup, r, "GemfireHeapPoller");
-        thread.setDaemon(true);
-        return thread;
-      }
-    };
-
-    this.pollerExecutor = Executors.newScheduledThreadPool(1, threadFactory);
+    this.pollerExecutor = LoggingExecutors.newScheduledThreadPool("GemfireHeapPoller", 1);
     this.pollerExecutor.scheduleAtFixedRate(new HeapPoller(), POLLER_INTERVAL, POLLER_INTERVAL,
         TimeUnit.MILLISECONDS);
 
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/control/InternalResourceManager.java b/geode-core/src/main/java/org/apache/geode/internal/cache/control/InternalResourceManager.java
index a3b324e..6d83141 100755
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/control/InternalResourceManager.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/control/InternalResourceManager.java
@@ -19,15 +19,11 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.logging.log4j.Logger;
 
@@ -42,8 +38,6 @@ import org.apache.geode.distributed.DistributedMember;
 import org.apache.geode.distributed.internal.DistributionAdvisor.Profile;
 import org.apache.geode.distributed.internal.DistributionConfig;
 import org.apache.geode.distributed.internal.DistributionManager;
-import org.apache.geode.distributed.internal.OverflowQueueWithDMStats;
-import org.apache.geode.distributed.internal.SerialQueuedExecutorWithDMStats;
 import org.apache.geode.internal.ClassPathLoader;
 import org.apache.geode.internal.cache.InternalCache;
 import org.apache.geode.internal.cache.PartitionedRegion;
@@ -52,7 +46,7 @@ import org.apache.geode.internal.cache.partitioned.LoadProbe;
 import org.apache.geode.internal.cache.partitioned.SizedBasedLoadProbe;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.monitoring.ThreadsMonitoring;
 
@@ -118,22 +112,8 @@ public class InternalResourceManager implements ResourceManager {
 
     // Create a new executor that other classes may use for handling resource
     // related tasks
-    final ThreadGroup threadGroup =
-        LoggingThreadGroup.createThreadGroup("ResourceManagerThreadGroup", logger);
-
-    ThreadFactory tf = new ThreadFactory() {
-      AtomicInteger ai = new AtomicInteger();
-
-      @Override
-      public Thread newThread(Runnable r) {
-        int tId = ai.getAndIncrement();
-        Thread thread = new Thread(threadGroup, r, "ResourceManagerRecoveryThread " + tId);
-        thread.setDaemon(true);
-        return thread;
-      }
-    };
-
-    this.scheduledExecutor = new ScheduledThreadPoolExecutor(MAX_RESOURCE_MANAGER_EXE_THREADS, tf);
+    this.scheduledExecutor = LoggingExecutors
+        .newScheduledThreadPool("ResourceManagerRecoveryThread ", MAX_RESOURCE_MANAGER_EXE_THREADS);
 
     // Initialize the load probe
     try {
@@ -145,22 +125,11 @@ public class InternalResourceManager implements ResourceManager {
 
     // Create a new executor the resource manager and the monitors it creates
     // can use to handle dispatching of notifications.
-    final ThreadGroup listenerInvokerthrdGrp =
-        LoggingThreadGroup.createThreadGroup("ResourceListenerInvokerThreadGroup", logger);
-
-    ThreadFactory eventProcessorFactory = new ThreadFactory() {
-      @Override
-      public Thread newThread(Runnable r) {
-        Thread thread = new Thread(listenerInvokerthrdGrp, r, "Notification Handler");
-        thread.setDaemon(true);
-        thread.setPriority(Thread.MAX_PRIORITY);
-        return thread;
-      }
-    };
-    BlockingQueue<Runnable> threadQ =
-        new OverflowQueueWithDMStats(this.stats.getResourceEventQueueStatHelper());
-    this.notifyExecutor = new SerialQueuedExecutorWithDMStats(threadQ,
-        this.stats.getResourceEventPoolStatHelper(), eventProcessorFactory, getThreadMonitorObj());
+    this.notifyExecutor =
+        LoggingExecutors.newSerialThreadPoolWithFeedStatistics("Notification Handler",
+            thread -> thread.setPriority(Thread.MAX_PRIORITY), null,
+            this.stats.getResourceEventPoolStatHelper(), getThreadMonitorObj(),
+            0, this.stats.getResourceEventQueueStatHelper());
 
     // Create the monitors
     Map<ResourceType, ResourceMonitor> tempMonitors = new HashMap<ResourceType, ResourceMonitor>();
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/control/OffHeapMemoryMonitor.java b/geode-core/src/main/java/org/apache/geode/internal/cache/control/OffHeapMemoryMonitor.java
index 60a901c..2d020ad 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/control/OffHeapMemoryMonitor.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/control/OffHeapMemoryMonitor.java
@@ -25,7 +25,7 @@ import org.apache.geode.internal.cache.control.MemoryThresholds.MemoryState;
 import org.apache.geode.internal.cache.control.ResourceAdvisor.ResourceManagerProfile;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.offheap.MemoryAllocator;
 import org.apache.geode.internal.offheap.MemoryUsageListener;
@@ -89,12 +89,9 @@ public class OffHeapMemoryMonitor implements MemoryMonitor, MemoryUsageListener
         return;
       }
 
-      ThreadGroup group =
-          LoggingThreadGroup.createThreadGroup("OffHeapMemoryMonitor Threads", logger);
-      Thread t = new Thread(group, this.offHeapMemoryUsageListener);
-      t.setName(t.getName() + " OffHeapMemoryListener");
+      Thread t =
+          new LoggingThread("OffHeapMemoryListener", this.offHeapMemoryUsageListener);
       t.setPriority(Thread.MAX_PRIORITY);
-      t.setDaemon(true);
       t.start();
       this.memoryListenerThread = t;
 
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/eviction/HeapEvictor.java b/geode-core/src/main/java/org/apache/geode/internal/cache/eviction/HeapEvictor.java
index 498a976..89ff04e 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/eviction/HeapEvictor.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/eviction/HeapEvictor.java
@@ -14,7 +14,6 @@
  */
 package org.apache.geode.internal.cache.eviction;
 
-import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.apache.geode.distributed.internal.DistributionConfig.GEMFIRE_PREFIX;
 
 import java.util.ArrayList;
@@ -23,18 +22,15 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
 
 import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
 import org.apache.logging.log4j.Logger;
 
 import org.apache.geode.cache.RegionDestroyedException;
-import org.apache.geode.distributed.internal.OverflowQueueWithDMStats;
+import org.apache.geode.distributed.internal.QueueStatHelper;
 import org.apache.geode.internal.cache.BucketRegion;
 import org.apache.geode.internal.cache.InternalCache;
 import org.apache.geode.internal.cache.LocalRegion;
@@ -45,7 +41,7 @@ import org.apache.geode.internal.cache.control.InternalResourceManager.ResourceT
 import org.apache.geode.internal.cache.control.MemoryEvent;
 import org.apache.geode.internal.cache.control.ResourceListener;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
 
 /**
  * Triggers centralized eviction(asynchronously) when the ResourceManager sends an eviction event
@@ -77,8 +73,6 @@ public class HeapEvictor implements ResourceListener<MemoryEvent> {
 
   private static final long TOTAL_BYTES_TO_EVICT_FROM_HEAP = setTotalBytesToEvictFromHeap();
 
-  private static final String EVICTOR_THREAD_GROUP_NAME = "EvictorThreadGroup";
-
   private static final String EVICTOR_THREAD_NAME = "EvictorThread";
 
   private final Object evictionLock = new Object();
@@ -87,7 +81,7 @@ public class HeapEvictor implements ResourceListener<MemoryEvent> {
 
   private final List<Integer> testTaskSetSizes = new ArrayList<>();
 
-  private final ThreadPoolExecutor evictorThreadPool;
+  private final ExecutorService evictorThreadPool;
 
   private final InternalCache cache;
 
@@ -109,30 +103,16 @@ public class HeapEvictor implements ResourceListener<MemoryEvent> {
   private volatile int numFastLoops;
 
   public HeapEvictor(final InternalCache cache) {
-    this(cache, EVICTOR_THREAD_GROUP_NAME, EVICTOR_THREAD_NAME);
+    this(cache, EVICTOR_THREAD_NAME);
   }
 
-  public HeapEvictor(final InternalCache cache, final String threadGroupName,
-      final String threadName) {
+  public HeapEvictor(final InternalCache cache, final String threadName) {
     this.cache = cache;
 
-    ThreadGroup evictorThreadGroup = LoggingThreadGroup.createThreadGroup(threadGroupName, logger);
-    ThreadFactory evictorThreadFactory = new ThreadFactory() {
-      private final AtomicInteger next = new AtomicInteger(0);
-
-      @Override
-      public Thread newThread(Runnable r) {
-        Thread thread = new Thread(evictorThreadGroup, r, threadName + next.incrementAndGet());
-        thread.setDaemon(true);
-        return thread;
-      }
-    };
-
     if (!DISABLE_HEAP_EVICTOR_THREAD_POOL) {
-      BlockingQueue<Runnable> poolQueue =
-          new OverflowQueueWithDMStats(this.cache.getCachePerfStats().getEvictionQueueStatHelper());
-      this.evictorThreadPool = new ThreadPoolExecutor(MAX_EVICTOR_THREADS, MAX_EVICTOR_THREADS, 15,
-          SECONDS, poolQueue, evictorThreadFactory);
+      QueueStatHelper poolStats = this.cache.getCachePerfStats().getEvictionQueueStatHelper();
+      this.evictorThreadPool = LoggingExecutors.newFixedThreadPoolWithTimeout(threadName,
+          MAX_EVICTOR_THREADS, 15, poolStats);
     } else {
       // disabled
       this.evictorThreadPool = null;
@@ -224,7 +204,7 @@ public class HeapEvictor implements ResourceListener<MemoryEvent> {
     }
   }
 
-  public ThreadPoolExecutor getEvictorThreadPool() {
+  public ExecutorService getEvictorThreadPool() {
     if (isRunning()) {
       return evictorThreadPool;
     }
@@ -264,12 +244,11 @@ public class HeapEvictor implements ResourceListener<MemoryEvent> {
   }
 
   private Set<RegionEvictorTask> createRegionEvictionTasks() {
-    ThreadPoolExecutor pool = getEvictorThreadPool();
-    if (pool == null) {
+    if (getEvictorThreadPool() == null) {
       return Collections.emptySet();
     }
 
-    int threadsAvailable = pool.getCorePoolSize();
+    int threadsAvailable = MAX_EVICTOR_THREADS;
     long bytesToEvictPerTask = getTotalBytesToEvict() / threadsAvailable;
     List<LocalRegion> allRegionList = getAllRegionList();
     if (allRegionList.isEmpty()) {
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/eviction/LRUListWithAsyncSorting.java b/geode-core/src/main/java/org/apache/geode/internal/cache/eviction/LRUListWithAsyncSorting.java
index d65d378..c9f89f3 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/eviction/LRUListWithAsyncSorting.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/eviction/LRUListWithAsyncSorting.java
@@ -16,9 +16,7 @@ package org.apache.geode.internal.cache.eviction;
 
 import java.util.Optional;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.logging.log4j.Logger;
@@ -30,7 +28,7 @@ import org.apache.geode.internal.cache.versions.RegionVersionVector;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.lang.SystemPropertyHelper;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.logging.log4j.LogMarker;
 
@@ -71,19 +69,7 @@ public class LRUListWithAsyncSorting extends AbstractEvictionList {
     if (threads < 1) {
       threads = Math.max((Runtime.getRuntime().availableProcessors() / 4), 1);
     }
-    final LoggingThreadGroup group =
-        LoggingThreadGroup.createThreadGroup("LRUListWithAsyncSorting Threads", logger);
-    ThreadFactory threadFactory = new ThreadFactory() {
-      private final AtomicInteger threadId = new AtomicInteger();
-
-      public Thread newThread(final Runnable command) {
-        Thread thread = new Thread(group, command,
-            "LRUListWithAsyncSortingThread" + this.threadId.incrementAndGet());
-        thread.setDaemon(true);
-        return thread;
-      }
-    };
-    return Executors.newFixedThreadPool(threads, threadFactory);
+    return LoggingExecutors.newFixedThreadPool("LRUListWithAsyncSortingThread", true, threads);
   }
 
   LRUListWithAsyncSorting(EvictionController controller) {
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/eviction/OffHeapEvictor.java b/geode-core/src/main/java/org/apache/geode/internal/cache/eviction/OffHeapEvictor.java
index 89f8e0e..5a8dc42 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/eviction/OffHeapEvictor.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/eviction/OffHeapEvictor.java
@@ -30,14 +30,12 @@ import org.apache.geode.internal.offheap.MemoryAllocator;
  */
 public class OffHeapEvictor extends HeapEvictor {
 
-  private static final String EVICTOR_THREAD_GROUP_NAME = "OffHeapEvictorThreadGroup";
-
   private static final String EVICTOR_THREAD_NAME = "OffHeapEvictorThread";
 
   private long bytesToEvictWithEachBurst;
 
   public OffHeapEvictor(final InternalCache cache) {
-    super(cache, EVICTOR_THREAD_GROUP_NAME, EVICTOR_THREAD_NAME);
+    super(cache, EVICTOR_THREAD_NAME);
     calculateEvictionBurst();
   }
 
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/ha/HARegionQueue.java b/geode-core/src/main/java/org/apache/geode/internal/cache/ha/HARegionQueue.java
index 860a410..cc16a5a 100755
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/ha/HARegionQueue.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/ha/HARegionQueue.java
@@ -96,6 +96,7 @@ import org.apache.geode.internal.cache.tier.sockets.HAEventWrapper;
 import org.apache.geode.internal.cache.tier.sockets.Handshake;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.logging.log4j.LogMarker;
 import org.apache.geode.internal.util.BlobHelper;
@@ -3666,28 +3667,26 @@ public class HARegionQueue implements RegionQueue {
 
         // Start a new thread which will update the clientMessagesRegion for
         // each of the HAEventWrapper instances present in the wrapperSet
-        Thread regionCleanupTask = new Thread(new Runnable() {
-          @Override
-          public void run() {
-            try {
-              Iterator iter = wrapperSet.iterator();
-              while (iter.hasNext()) {
-                Conflatable conflatable = (Conflatable) iter.next();
-                if (conflatable instanceof HAEventWrapper) {
-                  decAndRemoveFromHAContainer((HAEventWrapper) conflatable, "Destroy");
+        Thread regionCleanupTask =
+            new LoggingThread("HA Region Cleanup for " + regionName, false, () -> {
+              try {
+                Iterator iter = wrapperSet.iterator();
+                while (iter.hasNext()) {
+                  Conflatable conflatable = (Conflatable) iter.next();
+                  if (conflatable instanceof HAEventWrapper) {
+                    decAndRemoveFromHAContainer((HAEventWrapper) conflatable, "Destroy");
+                  }
+                }
+              } catch (CancelException ignore) {
+                return; // we're done
+              } catch (Exception e) {
+                if (logger.isDebugEnabled()) {
+                  logger.debug(
+                      "Exception in regionCleanupTask thread of HARegionQueue.updateHAContainer$run()",
+                      e);
                 }
               }
-            } catch (CancelException ignore) {
-              return; // we're done
-            } catch (Exception e) {
-              if (logger.isDebugEnabled()) {
-                logger.debug(
-                    "Exception in regionCleanupTask thread of HARegionQueue.updateHAContainer$run()",
-                    e);
-              }
-            }
-          }
-        }, "HA Region Cleanup for " + regionName);
+            });
         regionCleanupTask.start();
       }
     } catch (CancelException e) {
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/InternalClientMembership.java b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/InternalClientMembership.java
index 0005c8f..4356515 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/InternalClientMembership.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/InternalClientMembership.java
@@ -21,11 +21,8 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
 
 import org.apache.logging.log4j.Logger;
 
@@ -45,7 +42,7 @@ import org.apache.geode.internal.cache.tier.sockets.AcceptorImpl;
 import org.apache.geode.internal.cache.tier.sockets.ClientHealthMonitor;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.management.membership.ClientMembershipEvent;
 import org.apache.geode.management.membership.ClientMembershipListener;
@@ -81,10 +78,7 @@ public class InternalClientMembership {
    *
    * Access synchronized via {@link #systems}
    */
-  private static ThreadPoolExecutor executor;
-
-  private static final ThreadGroup threadGroup =
-      LoggingThreadGroup.createThreadGroup("ClientMembership Event Invoker Group", logger);
+  private static ExecutorService executor;
 
   /** List of connected <code>DistributedSystem</code>s */
   private static final List systems = new ArrayList(1);
@@ -420,7 +414,7 @@ public class InternalClientMembership {
   private static void notifyListeners(final DistributedMember member, final boolean client,
       final EventType typeOfEvent) {
     startMonitoring();
-    ThreadPoolExecutor queuedExecutor = executor;
+    ExecutorService queuedExecutor = executor;
     if (queuedExecutor == null) {
       return;
     }
@@ -533,16 +527,8 @@ public class InternalClientMembership {
   private static void ensureExecutorIsRunning() {
     // protected by calling method synchronized on systems
     if (executor == null) {
-      final ThreadGroup group = threadGroup;
-      ThreadFactory tf = new ThreadFactory() {
-        public Thread newThread(Runnable command) {
-          Thread thread = new Thread(group, command, "ClientMembership Event Invoker");
-          thread.setDaemon(true);
-          return thread;
-        }
-      };
-      LinkedBlockingQueue q = new LinkedBlockingQueue();
-      executor = new ThreadPoolExecutor(1, 1/* max unused */, 15, TimeUnit.SECONDS, q, tf);
+      executor =
+          LoggingExecutors.newFixedThreadPoolWithTimeout("ClientMembership Event Invoker", 1, 15);
     }
   }
 
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/AcceptorImpl.java b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/AcceptorImpl.java
index e89ff39..bb14f36 100755
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/AcceptorImpl.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/AcceptorImpl.java
@@ -48,12 +48,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.RejectedExecutionHandler;
-import java.util.concurrent.SynchronousQueue;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -73,7 +70,6 @@ import org.apache.geode.distributed.internal.DistributionConfig;
 import org.apache.geode.distributed.internal.DistributionManager;
 import org.apache.geode.distributed.internal.InternalDistributedSystem;
 import org.apache.geode.distributed.internal.LonerDistributionManager;
-import org.apache.geode.distributed.internal.PooledExecutorWithDMStats;
 import org.apache.geode.distributed.internal.ReplyProcessor21;
 import org.apache.geode.internal.HeapDataOutputStream;
 import org.apache.geode.internal.SystemTimer;
@@ -89,7 +85,10 @@ import org.apache.geode.internal.cache.tier.CommunicationMode;
 import org.apache.geode.internal.cache.wan.GatewayReceiverStats;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
+import org.apache.geode.internal.logging.LoggingThread;
+import org.apache.geode.internal.logging.LoggingThreadFactory.CommandWrapper;
+import org.apache.geode.internal.logging.LoggingThreadFactory.ThreadInitializer;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.monitoring.ThreadsMonitoring;
 import org.apache.geode.internal.net.SocketCreator;
@@ -117,16 +116,16 @@ public class AcceptorImpl implements Acceptor, Runnable, CommBufferPool {
   private final int maxConnections;
   private final int maxThreads;
 
-  private final ThreadPoolExecutor pool;
+  private final ExecutorService pool;
   /**
    * A pool used to process handshakes.
    */
-  private final ThreadPoolExecutor hsPool;
+  private final ExecutorService hsPool;
 
   /**
    * A pool used to process client-queue-initializations.
    */
-  private final ThreadPoolExecutor clientQueueInitPool;
+  private final ExecutorService clientQueueInitPool;
 
   /**
    * The port on which this acceptor listens for client connections
@@ -560,36 +559,13 @@ public class AcceptorImpl implements Acceptor, Runnable, CommBufferPool {
         (postAuthzFactoryName != null && postAuthzFactoryName.length() > 0) ? true : false;
   }
 
-  private ThreadPoolExecutor initializeHandshakerThreadPool() throws IOException {
-    String gName = "Handshaker " + serverSock.getInetAddress() + ":" + this.localPort;
-    final ThreadGroup socketThreadGroup = LoggingThreadGroup.createThreadGroup(gName, logger);
-
-    ThreadFactory socketThreadFactory = new ThreadFactory() {
-      AtomicInteger connNum = new AtomicInteger(-1);
-
-      @Override
-      public Thread newThread(Runnable command) {
-        String threadName = socketThreadGroup.getName() + " Thread " + connNum.incrementAndGet();
-        getStats().incAcceptThreadsCreated();
-        return new Thread(socketThreadGroup, command, threadName);
-      }
-    };
+  private ExecutorService initializeHandshakerThreadPool() throws IOException {
+    String threadName =
+        "Handshaker " + serverSock.getInetAddress() + ":" + this.localPort + " Thread ";
     try {
-      final BlockingQueue blockingQueue = new SynchronousQueue();
-      final RejectedExecutionHandler rejectedExecutionHandler = new RejectedExecutionHandler() {
-        public void rejectedExecution(Runnable r, ThreadPoolExecutor pool) {
-          try {
-            blockingQueue.put(r);
-          } catch (InterruptedException ex) {
-            Thread.currentThread().interrupt(); // preserve the state
-            throw new RejectedExecutionException(
-                LocalizedStrings.AcceptorImpl_INTERRUPTED.toLocalizedString(), ex);
-          }
-        }
-      };
       logger.warn("Handshaker max Pool size: " + HANDSHAKE_POOL_SIZE);
-      return new ThreadPoolExecutor(1, HANDSHAKE_POOL_SIZE, 60, TimeUnit.SECONDS, blockingQueue,
-          socketThreadFactory, rejectedExecutionHandler);
+      return LoggingExecutors.newThreadPoolWithSynchronousFeedThatHandlesRejection(threadName,
+          thread -> getStats().incAcceptThreadsCreated(), null, 1, HANDSHAKE_POOL_SIZE, 60);
     } catch (IllegalArgumentException poolInitException) {
       this.stats.close();
       this.serverSock.close();
@@ -598,69 +574,39 @@ public class AcceptorImpl implements Acceptor, Runnable, CommBufferPool {
     }
   }
 
-  private ThreadPoolExecutor initializeClientQueueInitializerThreadPool() throws IOException {
-    final ThreadGroup clientQueueThreadGroup =
-        LoggingThreadGroup.createThreadGroup("Client Queue Initialization ", logger);
-
-    ThreadFactory clientQueueThreadFactory = new ThreadFactory() {
-      AtomicInteger connNum = new AtomicInteger(-1);
-
-      @Override
-      public Thread newThread(final Runnable command) {
-        String threadName =
-            clientQueueThreadGroup.getName() + " Thread " + connNum.incrementAndGet();
-        Runnable runnable = new Runnable() {
-          public void run() {
-            try {
-              command.run();
-            } catch (CancelException e) {
-              logger.debug("Client Queue Initialization was canceled.", e);
-            }
-          }
-        };
-        return new Thread(clientQueueThreadGroup, runnable, threadName);
-      }
-    };
-    return new PooledExecutorWithDMStats(new SynchronousQueue(),
-        CLIENT_QUEUE_INITIALIZATION_POOL_SIZE, getStats().getCnxPoolHelper(),
-        clientQueueThreadFactory, 60000, getThreadMonitorObj());
-  }
-
-  private ThreadPoolExecutor initializeServerConnectionThreadPool() throws IOException {
-    String gName = "ServerConnection "
-        // + serverSock.getInetAddress()
-        + "on port " + this.localPort;
-    final ThreadGroup socketThreadGroup = LoggingThreadGroup.createThreadGroup(gName, logger);
-
-    ThreadFactory socketThreadFactory = new ThreadFactory() {
-      AtomicInteger connNum = new AtomicInteger(-1);
-
-      @Override
-      public Thread newThread(final Runnable command) {
-        String tName = socketThreadGroup.getName() + " Thread " + connNum.incrementAndGet();
-        getStats().incConnectionThreadsCreated();
-        Runnable r = new Runnable() {
-          public void run() {
-            try {
-              command.run();
-            } catch (CancelException e) { // bug 39463
-              // ignore
-            } finally {
-              ConnectionTable.releaseThreadsSockets();
-            }
+  private ExecutorService initializeClientQueueInitializerThreadPool() throws IOException {
+    return LoggingExecutors.newThreadPoolWithSynchronousFeed("Client Queue Initialization Thread ",
+        command -> {
+          try {
+            command.run();
+          } catch (CancelException e) {
+            logger.debug("Client Queue Initialization was canceled.", e);
           }
-        };
-        return new Thread(socketThreadGroup, r, tName);
+        }, CLIENT_QUEUE_INITIALIZATION_POOL_SIZE, getStats().getCnxPoolHelper(), 60000,
+        getThreadMonitorObj());
+  }
+
+  private ExecutorService initializeServerConnectionThreadPool() throws IOException {
+    String threadName = "ServerConnection on port " + this.localPort + " Thread ";
+    ThreadInitializer threadInitializer = thread -> getStats().incConnectionThreadsCreated();
+    CommandWrapper commandWrapper = command -> {
+      try {
+        command.run();
+      } catch (CancelException e) { // bug 39463
+        // ignore
+      } finally {
+        ConnectionTable.releaseThreadsSockets();
       }
     };
     try {
       if (isSelector()) {
-        return new PooledExecutorWithDMStats(new LinkedBlockingQueue(), this.maxThreads,
-            getStats().getCnxPoolHelper(), socketThreadFactory, Integer.MAX_VALUE,
-            getThreadMonitorObj());
+        return LoggingExecutors.newThreadPoolWithUnlimitedFeed(threadName, threadInitializer,
+            commandWrapper, this.maxThreads,
+            getStats().getCnxPoolHelper(), Integer.MAX_VALUE, getThreadMonitorObj());
       } else {
-        return new ThreadPoolExecutor(MINIMUM_MAX_CONNECTIONS, this.maxConnections, 0L,
-            TimeUnit.MILLISECONDS, new SynchronousQueue(), socketThreadFactory);
+        return LoggingExecutors.newThreadPoolWithSynchronousFeed(threadName, threadInitializer,
+            commandWrapper,
+            MINIMUM_MAX_CONNECTIONS, this.maxConnections, 0L);
       }
     } catch (IllegalArgumentException poolInitException) {
       this.stats.close();
@@ -716,26 +662,20 @@ public class AcceptorImpl implements Acceptor, Runnable, CommBufferPool {
 
   @Override
   public void start() throws IOException {
-    ThreadGroup tg = LoggingThreadGroup.createThreadGroup(
-        "Acceptor " + this.serverSock.getInetAddress() + ":" + this.localPort, logger);
-    thread = new Thread(tg, this, "Cache Server Acceptor " + this.serverSock.getInetAddress() + ":"
-        + this.localPort + " local port: " + this.serverSock.getLocalPort());
-
-    this.acceptorId = thread.getId();
-
     // This thread should not be a daemon to keep BridgeServers created
     // in code from exiting immediately.
+    thread =
+        new LoggingThread("Cache Server Acceptor " + this.serverSock.getInetAddress() + ":"
+            + this.localPort + " local port: " + this.serverSock.getLocalPort(), false, this);
+    this.acceptorId = thread.getId();
     thread.start();
 
     if (isSelector()) {
-      Runnable r = new Runnable() {
-        public void run() {
-          AcceptorImpl.this.runSelectorLoop();
-        }
-      };
       this.selectorThread =
-          new Thread(tg, r, "Cache Server Selector " + this.serverSock.getInetAddress() + ":"
-              + this.localPort + " local port: " + this.serverSock.getLocalPort());
+          new LoggingThread("Cache Server Selector " + this.serverSock.getInetAddress() + ":"
+              + this.localPort + " local port: " + this.serverSock.getLocalPort(),
+              false,
+              this::runSelectorLoop);
       this.selectorThread.start();
     }
     Set<PartitionedRegion> prs = this.cache.getPartitionedRegions();
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/CacheClientProxy.java b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/CacheClientProxy.java
index 84a67e3..0e9839e 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/CacheClientProxy.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/CacheClientProxy.java
@@ -98,7 +98,7 @@ import org.apache.geode.internal.cache.versions.VersionTag;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
 import org.apache.geode.internal.logging.LogWriterImpl;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.logging.log4j.LogMarker;
 import org.apache.geode.internal.security.AuthorizeRequestPP;
@@ -2234,7 +2234,7 @@ public class CacheClientProxy implements ClientSession {
    * the client by taking messsages from the message queue and sending them to the client over the
    * socket.
    */
-  static class MessageDispatcher extends Thread {
+  static class MessageDispatcher extends LoggingThread {
 
     /**
      * The queue of messages to be sent to the client
@@ -2295,9 +2295,7 @@ public class CacheClientProxy implements ClientSession {
      * @param name thread name for this dispatcher
      */
     protected MessageDispatcher(CacheClientProxy proxy, String name) throws CacheException {
-      super(LoggingThreadGroup.createThreadGroup(name, logger), name);
-
-      setDaemon(true);
+      super(name);
 
       this._proxy = proxy;
 
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/CacheClientUpdater.java b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/CacheClientUpdater.java
index ff3abf1..5fbe1f6 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/CacheClientUpdater.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/CacheClientUpdater.java
@@ -82,7 +82,7 @@ import org.apache.geode.internal.cache.versions.VersionTag;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.InternalLogWriter;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.logging.log4j.LogMarker;
 import org.apache.geode.internal.net.SocketCreator;
@@ -100,7 +100,7 @@ import org.apache.geode.security.GemFireSecurityException;
  *
  * @since GemFire 3.5
  */
-public class CacheClientUpdater extends Thread implements ClientUpdater, DisconnectListener {
+public class CacheClientUpdater extends LoggingThread implements ClientUpdater, DisconnectListener {
 
   private static final Logger logger = LogService.getLogger();
 
@@ -277,9 +277,7 @@ public class CacheClientUpdater extends Thread implements ClientUpdater, Disconn
       EndpointManager eManager, Endpoint endpoint, int handshakeTimeout,
       SocketCreator socketCreator) throws AuthenticationRequiredException,
       AuthenticationFailedException, ServerRefusedConnectionException {
-
-    super(LoggingThreadGroup.createThreadGroup("Client update thread"), name);
-    this.setDaemon(true);
+    super(name);
     this.system = (InternalDistributedSystem) ids;
     this.isDurableClient = handshake.getMembershipId().isDurable();
     this.isPrimary = primary;
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/ClientHealthMonitor.java b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/ClientHealthMonitor.java
index 934d501..38e6730 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/ClientHealthMonitor.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/ClientHealthMonitor.java
@@ -40,7 +40,7 @@ import org.apache.geode.internal.cache.TXManagerImpl;
 import org.apache.geode.internal.cache.tier.ServerSideHandshake;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 
 /**
@@ -700,7 +700,7 @@ public class ClientHealthMonitor {
    * Class <code>ClientHealthMonitorThread</code> is a <code>Thread</code> that verifies all clients
    * are still alive.
    */
-  class ClientHealthMonitorThread extends Thread {
+  class ClientHealthMonitorThread extends LoggingThread {
     private HeartbeatTimeoutCheck checkHeartbeat = (long currentTime, long lastHeartbeat,
         long allowedInterval) -> currentTime - lastHeartbeat > allowedInterval;
 
@@ -726,9 +726,7 @@ public class ClientHealthMonitor {
      *        client has died and interrupting its sockets
      */
     protected ClientHealthMonitorThread(int maximumTimeBetweenPings) {
-      super(LoggingThreadGroup.createThreadGroup("ClientHealthMonitor Thread Group", logger),
-          "ClientHealthMonitor Thread");
-      setDaemon(true);
+      super("ClientHealthMonitor Thread");
 
       // Set the client connection timeout
       this._maximumTimeBetweenPings = maximumTimeBetweenPings;
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/TcpServerFactory.java b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/TcpServerFactory.java
index 1f16dfd..e2e43cd 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/TcpServerFactory.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/TcpServerFactory.java
@@ -38,9 +38,9 @@ public class TcpServerFactory {
 
   public TcpServer makeTcpServer(int port, InetAddress bind_address, Properties sslConfig,
       DistributionConfigImpl cfg, TcpHandler handler, PoolStatHelper poolHelper,
-      ThreadGroup threadGroup, String threadName, InternalLocator internalLocator) {
+      String threadName, InternalLocator internalLocator) {
 
-    return new TcpServer(port, bind_address, sslConfig, cfg, handler, poolHelper, threadGroup,
+    return new TcpServer(port, bind_address, sslConfig, cfg, handler, poolHelper,
         threadName, internalLocator, clientProtocolServiceLoader);
   }
 }
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/command/ExecuteFunction66.java b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/command/ExecuteFunction66.java
index 6ef3576..efdec4e 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/command/ExecuteFunction66.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/command/ExecuteFunction66.java
@@ -16,9 +16,6 @@ package org.apache.geode.internal.cache.tier.sockets.command;
 
 import java.io.IOException;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.geode.InternalGemFireError;
 import org.apache.geode.cache.client.internal.ConnectionImpl;
@@ -51,6 +48,7 @@ import org.apache.geode.internal.cache.tier.sockets.Message;
 import org.apache.geode.internal.cache.tier.sockets.Part;
 import org.apache.geode.internal.cache.tier.sockets.ServerConnection;
 import org.apache.geode.internal.i18n.LocalizedStrings;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.security.AuthorizeRequest;
 import org.apache.geode.internal.security.SecurityService;
@@ -64,15 +62,8 @@ public class ExecuteFunction66 extends BaseCommand {
 
   protected static volatile boolean ASYNC_TX_WARNING_ISSUED = false;
 
-  static final ExecutorService execService = Executors.newCachedThreadPool(new ThreadFactory() {
-    AtomicInteger threadNum = new AtomicInteger();
-
-    public Thread newThread(final Runnable r) {
-      Thread result = new Thread(r, "Function Execution Thread-" + threadNum.incrementAndGet());
-      result.setDaemon(true);
-      return result;
-    }
-  });
+  static final ExecutorService execService =
+      LoggingExecutors.newCachedThreadPool("Function Execution Thread-", true);
 
   public static Command getCommand() {
     return singleton;
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/wan/AbstractGatewaySender.java b/geode-core/src/main/java/org/apache/geode/internal/cache/wan/AbstractGatewaySender.java
index b099eb1..d331da9 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/wan/AbstractGatewaySender.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/wan/AbstractGatewaySender.java
@@ -71,7 +71,7 @@ import org.apache.geode.internal.cache.wan.serial.ConcurrentSerialGatewaySenderE
 import org.apache.geode.internal.cache.xmlcache.CacheCreation;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.offheap.Releasable;
 import org.apache.geode.internal.offheap.annotations.Released;
@@ -672,9 +672,7 @@ public abstract class AbstractGatewaySender implements InternalGatewaySender, Di
         }
       }
     };
-    ThreadGroup tg = LoggingThreadGroup.createThreadGroup("Proxy Stomper Group", logger);
-    Thread t = new Thread(tg, stomper, "GatewaySender Proxy Stomper");
-    t.setDaemon(true);
+    Thread t = new LoggingThread("GatewaySender Proxy Stomper", stomper);
     t.start();
     try {
       t.join(GATEWAY_SENDER_TIMEOUT * 1000);
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/wan/AbstractGatewaySenderEventProcessor.java b/geode-core/src/main/java/org/apache/geode/internal/cache/wan/AbstractGatewaySenderEventProcessor.java
index ea2f603..2310332 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/wan/AbstractGatewaySenderEventProcessor.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/wan/AbstractGatewaySenderEventProcessor.java
@@ -55,7 +55,7 @@ import org.apache.geode.internal.cache.wan.parallel.ParallelGatewaySenderQueue;
 import org.apache.geode.internal.cache.wan.serial.SerialGatewaySenderQueue;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.monitoring.ThreadsMonitoring;
 import org.apache.geode.pdx.internal.PeerTypeRegistration;
@@ -68,7 +68,7 @@ import org.apache.geode.pdx.internal.PeerTypeRegistration;
  *
  * @since GemFire 7.0
  */
-public abstract class AbstractGatewaySenderEventProcessor extends Thread
+public abstract class AbstractGatewaySenderEventProcessor extends LoggingThread
     implements GatewaySenderEventProcessor {
 
   private static final Logger logger = LogService.getLogger();
@@ -147,9 +147,9 @@ public abstract class AbstractGatewaySenderEventProcessor extends Thread
    */
   private int batchSize;
 
-  public AbstractGatewaySenderEventProcessor(LoggingThreadGroup createThreadGroup, String string,
+  public AbstractGatewaySenderEventProcessor(String string,
       GatewaySender sender, ThreadsMonitoring tMonitoring) {
-    super(createThreadGroup, string);
+    super(string);
     this.sender = (AbstractGatewaySender) sender;
     this.batchSize = sender.getBatchSize();
     this.threadMonitoring = tMonitoring;
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/wan/GatewaySenderAdvisor.java b/geode-core/src/main/java/org/apache/geode/internal/cache/wan/GatewaySenderAdvisor.java
index 3eb1dd1..9a9402b 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/wan/GatewaySenderAdvisor.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/wan/GatewaySenderAdvisor.java
@@ -47,7 +47,7 @@ import org.apache.geode.internal.cache.InternalCache;
 import org.apache.geode.internal.cache.UpdateAttributesProcessor;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 
 public class GatewaySenderAdvisor extends DistributionAdvisor {
@@ -63,9 +63,6 @@ public class GatewaySenderAdvisor extends DistributionAdvisor {
 
   private Thread lockObtainingThread;
 
-  private final ThreadGroup threadGroup =
-      LoggingThreadGroup.createThreadGroup("GatewaySenderAdvisor Threads");
-
   private AbstractGatewaySender sender;
 
   private GatewaySenderAdvisor(DistributionAdvisee sender) {
@@ -423,46 +420,42 @@ public class GatewaySenderAdvisor extends DistributionAdvisor {
   }
 
   public void launchLockObtainingVolunteerThread() {
-    this.lockObtainingThread = new Thread(threadGroup, new Runnable() {
-      @SuppressWarnings("synthetic-access")
-      public void run() {
-        GatewaySenderAdvisor.this.sender.getLifeCycleLock().readLock().lock();
-        try {
-          // Attempt to obtain the lock
-          if (!(GatewaySenderAdvisor.this.sender.isRunning())) {
-            return;
-          }
+    String threadName = "Gateway Sender Primary Lock Acquisition Thread Volunteer";
+    this.lockObtainingThread = new LoggingThread(threadName, () -> {
+      GatewaySenderAdvisor.this.sender.getLifeCycleLock().readLock().lock();
+      try {
+        // Attempt to obtain the lock
+        if (!(GatewaySenderAdvisor.this.sender.isRunning())) {
+          return;
+        }
+        if (logger.isDebugEnabled()) {
+          logger.debug("{}: Obtaining the lock on {}", this, GatewaySenderAdvisor.this.lockToken);
+        }
+
+        if (volunteerForPrimary()) {
           if (logger.isDebugEnabled()) {
-            logger.debug("{}: Obtaining the lock on {}", this, GatewaySenderAdvisor.this.lockToken);
+            logger.debug("{}: Obtained the lock on {}", this,
+                GatewaySenderAdvisor.this.lockToken);
           }
+          logger.info(LocalizedMessage.create(
+              LocalizedStrings.GatewaySender_0_IS_BECOMING_PRIMARY_GATEWAY_Sender,
+              GatewaySenderAdvisor.this));
 
-          if (volunteerForPrimary()) {
-            if (logger.isDebugEnabled()) {
-              logger.debug("{}: Obtained the lock on {}", this,
-                  GatewaySenderAdvisor.this.lockToken);
-            }
-            logger.info(LocalizedMessage.create(
-                LocalizedStrings.GatewaySender_0_IS_BECOMING_PRIMARY_GATEWAY_Sender,
-                GatewaySenderAdvisor.this));
-
-            // As soon as the lock is obtained, set primary
-            GatewaySenderAdvisor.this.makePrimary();
-          }
-        } catch (CancelException e) {
-          // no action necessary
-        } catch (Exception e) {
-          if (!sender.getStopper().isCancelInProgress()) {
-            logger.fatal(LocalizedMessage.create(
-                LocalizedStrings.GatewaySenderAdvisor_0_THE_THREAD_TO_OBTAIN_THE_FAILOVER_LOCK_WAS_INTERRUPTED__THIS_GATEWAY_SENDER_WILL_NEVER_BECOME_THE_PRIMARY,
-                GatewaySenderAdvisor.this), e);
-          }
-        } finally {
-          GatewaySenderAdvisor.this.sender.getLifeCycleLock().readLock().unlock();
+          // As soon as the lock is obtained, set primary
+          GatewaySenderAdvisor.this.makePrimary();
+        }
+      } catch (CancelException e) {
+        // no action necessary
+      } catch (Exception e) {
+        if (!sender.getStopper().isCancelInProgress()) {
+          logger.fatal(LocalizedMessage.create(
+              LocalizedStrings.GatewaySenderAdvisor_0_THE_THREAD_TO_OBTAIN_THE_FAILOVER_LOCK_WAS_INTERRUPTED__THIS_GATEWAY_SENDER_WILL_NEVER_BECOME_THE_PRIMARY,
+              GatewaySenderAdvisor.this), e);
         }
+      } finally {
+        GatewaySenderAdvisor.this.sender.getLifeCycleLock().readLock().unlock();
       }
-    }, "Gateway Sender Primary Lock Acquisition Thread Volunteer");
-
-    this.lockObtainingThread.setDaemon(true);
+    });
     this.lockObtainingThread.start();
   }
 
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/wan/parallel/ConcurrentParallelGatewaySenderEventProcessor.java b/geode-core/src/main/java/org/apache/geode/internal/cache/wan/parallel/ConcurrentParallelGatewaySenderEventProcessor.java
index 5f0dea8..c2ee562 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/wan/parallel/ConcurrentParallelGatewaySenderEventProcessor.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/wan/parallel/ConcurrentParallelGatewaySenderEventProcessor.java
@@ -22,9 +22,7 @@ import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
-import java.util.concurrent.ThreadFactory;
 
 import org.apache.logging.log4j.Logger;
 
@@ -50,7 +48,7 @@ import org.apache.geode.internal.cache.wan.GatewaySenderEventImpl;
 import org.apache.geode.internal.cache.wan.GatewaySenderException;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.monitoring.ThreadsMonitoring;
 
@@ -76,9 +74,7 @@ public class ConcurrentParallelGatewaySenderEventProcessor
 
   public ConcurrentParallelGatewaySenderEventProcessor(AbstractGatewaySender sender,
       ThreadsMonitoring tMonitoring) {
-    super(
-        LoggingThreadGroup.createThreadGroup("Event Processor for GatewaySender_" + sender.getId()),
-        "Event Processor for GatewaySender_" + sender.getId(), sender, tMonitoring);
+    super("Event Processor for GatewaySender_" + sender.getId(), sender, tMonitoring);
     // initializeMessageQueue(sender.getId());
     logger.info("ConcurrentParallelGatewaySenderEventProcessor: dispatcher threads {}",
         sender.getDispatcherThreads());
@@ -111,7 +107,6 @@ public class ConcurrentParallelGatewaySenderEventProcessor
 
     // this.queue = parallelQueue;
     this.queue = new ConcurrentParallelGatewaySenderQueue(sender, this.processors);
-    setDaemon(true);
   }
 
   protected void createProcessors(int dispatcherThreads, Set<Region> targetRs) {
@@ -239,24 +234,14 @@ public class ConcurrentParallelGatewaySenderEventProcessor
 
     setIsStopped(true);
 
-    final LoggingThreadGroup loggingThreadGroup = LoggingThreadGroup
-        .createThreadGroup("ConcurrentParallelGatewaySenderEventProcessor Logger Group", logger);
-
-    ThreadFactory threadFactory = new ThreadFactory() {
-      public Thread newThread(final Runnable task) {
-        final Thread thread = new Thread(loggingThreadGroup, task,
-            "ConcurrentParallelGatewaySenderEventProcessor Stopper Thread");
-        thread.setDaemon(true);
-        return thread;
-      }
-    };
-
     List<SenderStopperCallable> stopperCallables = new ArrayList<SenderStopperCallable>();
     for (ParallelGatewaySenderEventProcessor parallelProcessor : this.processors) {
       stopperCallables.add(new SenderStopperCallable(parallelProcessor));
     }
 
-    ExecutorService stopperService = Executors.newFixedThreadPool(processors.length, threadFactory);
+    ExecutorService stopperService = LoggingExecutors.newFixedThreadPool(
+        "ConcurrentParallelGatewaySenderEventProcessor Stopper Thread",
+        true, processors.length);
     try {
       List<Future<Boolean>> futures = stopperService.invokeAll(stopperCallables);
       for (Future<Boolean> f : futures) {
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/wan/parallel/ParallelGatewaySenderEventProcessor.java b/geode-core/src/main/java/org/apache/geode/internal/cache/wan/parallel/ParallelGatewaySenderEventProcessor.java
index 3ad4b03..07d6478 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/wan/parallel/ParallelGatewaySenderEventProcessor.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/wan/parallel/ParallelGatewaySenderEventProcessor.java
@@ -37,7 +37,6 @@ import org.apache.geode.internal.cache.wan.AbstractGatewaySenderEventProcessor;
 import org.apache.geode.internal.cache.wan.GatewaySenderEventCallbackDispatcher;
 import org.apache.geode.internal.cache.wan.GatewaySenderEventImpl;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
 import org.apache.geode.internal.monitoring.ThreadsMonitoring;
 
 public class ParallelGatewaySenderEventProcessor extends AbstractGatewaySenderEventProcessor {
@@ -49,13 +48,10 @@ public class ParallelGatewaySenderEventProcessor extends AbstractGatewaySenderEv
 
   protected ParallelGatewaySenderEventProcessor(AbstractGatewaySender sender,
       ThreadsMonitoring tMonitoring) {
-    super(LoggingThreadGroup
-        .createThreadGroup("Event Processor for GatewaySender_" + sender.getId(), logger),
-        "Event Processor for GatewaySender_" + sender.getId(), sender, tMonitoring);
+    super("Event Processor for GatewaySender_" + sender.getId(), sender, tMonitoring);
     this.index = 0;
     this.nDispatcher = 1;
     initializeMessageQueue(sender.getId());
-    setDaemon(true);
   }
 
   /**
@@ -63,14 +59,10 @@ public class ParallelGatewaySenderEventProcessor extends AbstractGatewaySenderEv
    */
   protected ParallelGatewaySenderEventProcessor(AbstractGatewaySender sender,
       Set<Region> userRegions, int id, int nDispatcher, ThreadsMonitoring tMonitoring) {
-    super(
-        LoggingThreadGroup.createThreadGroup("Event Processor for GatewaySender_" + sender.getId(),
-            logger),
-        "Event Processor for GatewaySender_" + sender.getId() + "_" + id, sender, tMonitoring);
+    super("Event Processor for GatewaySender_" + sender.getId() + "_" + id, sender, tMonitoring);
     this.index = id;
     this.nDispatcher = nDispatcher;
     initializeMessageQueue(sender.getId());
-    setDaemon(true);
   }
 
   @Override
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/wan/parallel/ParallelGatewaySenderQueue.java b/geode-core/src/main/java/org/apache/geode/internal/cache/wan/parallel/ParallelGatewaySenderQueue.java
index 057697a..4333575 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/wan/parallel/ParallelGatewaySenderQueue.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/wan/parallel/ParallelGatewaySenderQueue.java
@@ -29,9 +29,7 @@ import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 
 import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
@@ -81,7 +79,7 @@ import org.apache.geode.internal.cache.wan.GatewaySenderException;
 import org.apache.geode.internal.cache.wan.GatewaySenderStats;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.size.SingleObjectSizer;
 import org.apache.geode.internal.util.concurrent.StoppableCondition;
@@ -614,19 +612,9 @@ public class ParallelGatewaySenderQueue implements RegionQueue {
    * processors available to the JVM.
    */
   private void initializeConflationThreadPool() {
-    final LoggingThreadGroup loggingThreadGroup =
-        LoggingThreadGroup.createThreadGroup("WAN Queue Conflation Logger Group", logger);
-
-    final ThreadFactory threadFactory = new ThreadFactory() {
-      public Thread newThread(final Runnable task) {
-        final Thread thread = new Thread(loggingThreadGroup, task, "WAN Queue Conflation Thread");
-        thread.setDaemon(true);
-        return thread;
-      }
-    };
-
+    int poolSize = Runtime.getRuntime().availableProcessors();
     conflationExecutor =
-        Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), threadFactory);
+        LoggingExecutors.newFixedThreadPool("WAN Queue Conflation Thread", true, poolSize);
   }
 
   /**
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/wan/serial/ConcurrentSerialGatewaySenderEventProcessor.java b/geode-core/src/main/java/org/apache/geode/internal/cache/wan/serial/ConcurrentSerialGatewaySenderEventProcessor.java
index fc1daf4..52edc63 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/wan/serial/ConcurrentSerialGatewaySenderEventProcessor.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/wan/serial/ConcurrentSerialGatewaySenderEventProcessor.java
@@ -22,9 +22,7 @@ import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
-import java.util.concurrent.ThreadFactory;
 import java.util.stream.Collectors;
 
 import org.apache.logging.log4j.Logger;
@@ -49,7 +47,7 @@ import org.apache.geode.internal.cache.wan.GatewaySenderEventDispatcher;
 import org.apache.geode.internal.cache.wan.GatewaySenderException;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.monitoring.ThreadsMonitoring;
 import org.apache.geode.internal.offheap.annotations.Released;
@@ -70,9 +68,7 @@ public class ConcurrentSerialGatewaySenderEventProcessor
 
   public ConcurrentSerialGatewaySenderEventProcessor(AbstractGatewaySender sender,
       ThreadsMonitoring tMonitoring) {
-    super(LoggingThreadGroup
-        .createThreadGroup("Event Processor for GatewaySender_" + sender.getId(), logger),
-        "Event Processor for GatewaySender_" + sender.getId(), sender, tMonitoring);
+    super("Event Processor for GatewaySender_" + sender.getId(), sender, tMonitoring);
     this.sender = sender;
 
     initializeMessageQueue(sender.getId());
@@ -80,7 +76,6 @@ public class ConcurrentSerialGatewaySenderEventProcessor
     for (SerialGatewaySenderEventProcessor processor : processors) {
       queues.add(processor.getQueue());
     }
-    setDaemon(true);
   }
 
   @Override
@@ -284,24 +279,14 @@ public class ConcurrentSerialGatewaySenderEventProcessor
 
     setIsStopped(true);
 
-    final LoggingThreadGroup loggingThreadGroup = LoggingThreadGroup
-        .createThreadGroup("ConcurrentSerialGatewaySenderEventProcessor Logger Group", logger);
-
-    ThreadFactory threadFactory = new ThreadFactory() {
-      public Thread newThread(final Runnable task) {
-        final Thread thread = new Thread(loggingThreadGroup, task,
-            "ConcurrentSerialGatewaySenderEventProcessor Stopper Thread");
-        thread.setDaemon(true);
-        return thread;
-      }
-    };
-
     List<SenderStopperCallable> stopperCallables = new ArrayList<SenderStopperCallable>();
     for (SerialGatewaySenderEventProcessor serialProcessor : this.processors) {
       stopperCallables.add(new SenderStopperCallable(serialProcessor));
     }
 
-    ExecutorService stopperService = Executors.newFixedThreadPool(processors.size(), threadFactory);
+    ExecutorService stopperService = LoggingExecutors.newFixedThreadPool(
+        "ConcurrentSerialGatewaySenderEventProcessor Stopper Thread",
+        true, processors.size());
     try {
       List<Future<Boolean>> futures = stopperService.invokeAll(stopperCallables);
       for (Future<Boolean> f : futures) {
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/wan/serial/SerialGatewaySenderEventProcessor.java b/geode-core/src/main/java/org/apache/geode/internal/cache/wan/serial/SerialGatewaySenderEventProcessor.java
index b2f9838..17a4bdd 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/wan/serial/SerialGatewaySenderEventProcessor.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/wan/serial/SerialGatewaySenderEventProcessor.java
@@ -23,10 +23,6 @@ import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 import org.apache.logging.log4j.Logger;
@@ -57,7 +53,7 @@ import org.apache.geode.internal.cache.wan.GatewaySenderEventImpl;
 import org.apache.geode.internal.cache.wan.GatewaySenderStats;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.monitoring.ThreadsMonitoring;
 import org.apache.geode.pdx.internal.PeerTypeRegistration;
@@ -113,14 +109,12 @@ public class SerialGatewaySenderEventProcessor extends AbstractGatewaySenderEven
 
   public SerialGatewaySenderEventProcessor(AbstractGatewaySender sender, String id,
       ThreadsMonitoring tMonitoring) {
-    super(LoggingThreadGroup.createThreadGroup("Event Processor for GatewaySender_" + id, logger),
-        "Event Processor for GatewaySender_" + id, sender, tMonitoring);
+    super("Event Processor for GatewaySender_" + id, sender, tMonitoring);
 
     this.unprocessedEvents = new LinkedHashMap<EventID, EventWrapper>();
     this.unprocessedTokens = new LinkedHashMap<EventID, Long>();
 
     initializeMessageQueue(id);
-    setDaemon(true);
   }
 
   @Override
@@ -820,20 +814,8 @@ public class SerialGatewaySenderEventProcessor extends AbstractGatewaySenderEven
    * Initialize the Executor that handles listener events. Only used by non-primary gateway senders
    */
   private void initializeListenerExecutor() {
-    // Create the ThreadGroups
-    final ThreadGroup loggerGroup =
-        LoggingThreadGroup.createThreadGroup("Gateway Listener Group", logger);
-
-    // Create the Executor
-    ThreadFactory tf = new ThreadFactory() {
-      public Thread newThread(Runnable command) {
-        Thread thread = new Thread(loggerGroup, command, "Queued Gateway Listener Thread");
-        thread.setDaemon(true);
-        return thread;
-      }
-    };
-    LinkedBlockingQueue<Runnable> q = new LinkedBlockingQueue<Runnable>();
-    this.executor = new ThreadPoolExecutor(1, 1/* max unused */, 120, TimeUnit.SECONDS, q, tf);
+    this.executor =
+        LoggingExecutors.newFixedThreadPoolWithTimeout("Queued Gateway Listener Thread", 1, 120);
   }
 
   private void shutdownListenerExecutor() {
diff --git a/geode-core/src/main/java/org/apache/geode/internal/datasource/AbstractPoolCache.java b/geode-core/src/main/java/org/apache/geode/internal/datasource/AbstractPoolCache.java
index c2f9b90..b8dfa87 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/datasource/AbstractPoolCache.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/datasource/AbstractPoolCache.java
@@ -32,7 +32,7 @@ import org.apache.geode.CancelException;
 import org.apache.geode.SystemFailure;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 
 /**
  * AbstractPoolCache implements the ConnectionPoolCache interface. This is base class for the all
@@ -87,9 +87,7 @@ public abstract class AbstractPoolCache implements ConnectionPoolCache, Serializ
     INIT_LIMIT = Math.min(configs.getInitialPoolSize(), MAX_LIMIT);
     configProps = configs;
     cleaner = this.new ConnectionCleanUpThread();
-    ThreadGroup group = LoggingThreadGroup.createThreadGroup("Cleaner threads");
-    th = new Thread(group, cleaner);
-    th.setDaemon(true);
+    th = new LoggingThread("ConnectionCleanUpThread", cleaner);
     th.start();
   }
 
diff --git a/geode-core/src/main/java/org/apache/geode/internal/jta/TransactionManagerImpl.java b/geode-core/src/main/java/org/apache/geode/internal/jta/TransactionManagerImpl.java
index 54cc826..086a7f0 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/jta/TransactionManagerImpl.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/jta/TransactionManagerImpl.java
@@ -51,7 +51,7 @@ import org.apache.geode.CancelException;
 import org.apache.geode.i18n.LogWriterI18n;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 
 @Deprecated
 public class TransactionManagerImpl implements TransactionManager, Serializable {
@@ -114,11 +114,8 @@ public class TransactionManagerImpl implements TransactionManager, Serializable
    * Constructs a new TransactionManagerImpl
    */
   private TransactionManagerImpl() {
-    cleaner = this.new TransactionTimeOutThread();
-    ThreadGroup group = LoggingThreadGroup.createThreadGroup(
-        LocalizedStrings.TransactionManagerImpl_CLEAN_UP_THREADS.toLocalizedString());
-    cleanUpThread = new Thread(group, cleaner, "GlobalTXTimeoutMonitor");
-    cleanUpThread.setDaemon(true);
+    cleaner = new TransactionTimeOutThread();
+    cleanUpThread = new LoggingThread("GlobalTXTimeoutMonitor", cleaner);
     cleanUpThread.start();
   }
 
diff --git a/geode-core/src/main/java/org/apache/geode/internal/logging/LogWriterImpl.java b/geode-core/src/main/java/org/apache/geode/internal/logging/LogWriterImpl.java
index 742b9fc..efa9574 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/logging/LogWriterImpl.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/logging/LogWriterImpl.java
@@ -1092,38 +1092,37 @@ public abstract class LogWriterImpl implements InternalLogWriter {
     if (targetThread == null) {
       return;
     }
-    Thread watcherThread = new Thread("Stack Tracer for '" + targetThread.getName() + "'") {
-      @Override
-      public void run() {
-        while (!done.get()) {
-          try {
-            Thread.sleep(500);
-          } catch (InterruptedException e) {
-            return;
-          }
-          if (!done.get() && targetThread.isAlive()) {
-            StringBuilder sb = new StringBuilder(500);
-            if (toStdout) {
-              sb.append("[trace ").append(getTimeStamp()).append("] ");
+    Thread watcherThread =
+        new LoggingThread("Stack Tracer for '" + targetThread.getName() + "'", false, () -> {
+          while (!done.get()) {
+            try {
+              Thread.sleep(500);
+            } catch (InterruptedException e) {
+              return;
             }
-            StackTraceElement[] els = targetThread.getStackTrace();
-            sb.append("Stack trace for '").append(targetThread).append("'").append(LINE_SEPARATOR);
-            if (els.length > 0) {
-              for (int i = 0; i < els.length; i++) {
-                sb.append("\tat ").append(els[i]).append(LINE_SEPARATOR);
+            if (!done.get() && targetThread.isAlive()) {
+              StringBuilder sb = new StringBuilder(500);
+              if (toStdout) {
+                sb.append("[trace ").append(getTimeStamp()).append("] ");
+              }
+              StackTraceElement[] els = targetThread.getStackTrace();
+              sb.append("Stack trace for '").append(targetThread).append("'")
+                  .append(LINE_SEPARATOR);
+              if (els.length > 0) {
+                for (int i = 0; i < els.length; i++) {
+                  sb.append("\tat ").append(els[i]).append(LINE_SEPARATOR);
+                }
+              } else {
+                sb.append("    no stack").append(LINE_SEPARATOR);
+              }
+              if (toStdout) {
+                System.out.println(sb);
+              } else {
+                info(LocalizedStrings.DEBUG, sb.toString());
               }
-            } else {
-              sb.append("    no stack").append(LINE_SEPARATOR);
-            }
-            if (toStdout) {
-              System.out.println(sb);
-            } else {
-              info(LocalizedStrings.DEBUG, sb.toString());
             }
           }
-        }
-      }
-    };
+        });
     watcherThread.start();
   }
 
diff --git a/geode-core/src/main/java/org/apache/geode/internal/logging/LoggingExecutors.java b/geode-core/src/main/java/org/apache/geode/internal/logging/LoggingExecutors.java
new file mode 100644
index 0000000..6d9f644
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/internal/logging/LoggingExecutors.java
@@ -0,0 +1,306 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geode.internal.logging;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory;
+import java.util.concurrent.ForkJoinWorkerThread;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.geode.distributed.internal.FunctionExecutionPooledExecutor;
+import org.apache.geode.distributed.internal.OverflowQueueWithDMStats;
+import org.apache.geode.distributed.internal.PoolStatHelper;
+import org.apache.geode.distributed.internal.PooledExecutorWithDMStats;
+import org.apache.geode.distributed.internal.QueueStatHelper;
+import org.apache.geode.distributed.internal.SerialQueuedExecutorWithDMStats;
+import org.apache.geode.internal.ScheduledThreadPoolExecutorWithKeepAlive;
+import org.apache.geode.internal.logging.LoggingThreadFactory.CommandWrapper;
+import org.apache.geode.internal.logging.LoggingThreadFactory.ThreadInitializer;
+import org.apache.geode.internal.monitoring.ThreadsMonitoring;
+
+/**
+ * Utility class that creates instances of ExecutorService
+ * whose threads will always log uncaught exceptions.
+ */
+public class LoggingExecutors {
+
+  public static ExecutorService newSerialThreadPool(String threadName,
+      ThreadInitializer threadInitializer, CommandWrapper commandWrapper,
+      PoolStatHelper stats, ThreadsMonitoring threadsMonitoring, BlockingQueue<Runnable> feed) {
+    ThreadFactory threadFactory =
+        new LoggingThreadFactory(threadName, threadInitializer, commandWrapper);
+    return new SerialQueuedExecutorWithDMStats(feed, stats, threadFactory, threadsMonitoring);
+  }
+
+  public static ExecutorService newSerialThreadPoolWithFeedStatistics(String threadName,
+      ThreadInitializer threadInitializer, CommandWrapper commandWrapper,
+      PoolStatHelper poolStats,
+      ThreadsMonitoring threadsMonitoring, int feedSize, QueueStatHelper feedStats) {
+    BlockingQueue<Runnable> feed = createFeedWithStatistics(feedSize, feedStats);
+    return newSerialThreadPool(threadName, threadInitializer, commandWrapper, poolStats,
+        threadsMonitoring, feed);
+  }
+
+  public static ExecutorService newSerialThreadPoolWithUnlimitedFeed(String threadName,
+      ThreadInitializer threadInitializer,
+      CommandWrapper commandWrapper, PoolStatHelper stats, ThreadsMonitoring threadsMonitoring) {
+    LinkedBlockingQueue<Runnable> feed = new LinkedBlockingQueue<>();
+    return newSerialThreadPool(threadName, threadInitializer, commandWrapper, stats,
+        threadsMonitoring, feed);
+  }
+
+  public static ExecutorService newThreadPoolWithUnlimitedFeed(String threadName,
+      ThreadInitializer threadInitializer,
+      CommandWrapper commandWrapper, int poolSize,
+      PoolStatHelper poolStats, int msTimeout, ThreadsMonitoring threadsMonitoring) {
+    LinkedBlockingQueue<Runnable> feed = new LinkedBlockingQueue<>();
+    ThreadFactory threadFactory =
+        new LoggingThreadFactory(threadName, threadInitializer, commandWrapper);
+    return new PooledExecutorWithDMStats(feed, poolSize, poolStats, threadFactory, msTimeout,
+        threadsMonitoring);
+  }
+
+  public static ExecutorService newThreadPoolWithFixedFeed(String threadName,
+      CommandWrapper commandWrapper,
+      int poolSize, PoolStatHelper poolStats, int msTimeout, ThreadsMonitoring threadsMonitoring,
+      int feedSize) {
+    ArrayBlockingQueue<Runnable> feed = new ArrayBlockingQueue<>(feedSize);
+    ThreadFactory threadFactory = new LoggingThreadFactory(threadName, commandWrapper);
+    return new PooledExecutorWithDMStats(feed, poolSize, poolStats, threadFactory, msTimeout,
+        threadsMonitoring);
+  }
+
+  public static ExecutorService newFunctionThreadPoolWithFeedStatistics(String threadName,
+      ThreadInitializer threadInitializer, CommandWrapper commandWrapper,
+      int poolSize, PoolStatHelper poolStats,
+      ThreadsMonitoring threadsMonitoring, int feedSize, QueueStatHelper feedStats) {
+    BlockingQueue<Runnable> feed = createFeedWithStatistics(feedSize, feedStats);
+    ThreadFactory threadFactory =
+        new LoggingThreadFactory(threadName, threadInitializer, commandWrapper);
+    return new FunctionExecutionPooledExecutor(feed, poolSize, poolStats, threadFactory, true,
+        threadsMonitoring);
+  }
+
+  private static BlockingQueue<Runnable> createFeedWithStatistics(int feedSize,
+      QueueStatHelper feedStats) {
+    BlockingQueue<Runnable> feed;
+    if (feedSize == 0) {
+      feed = new OverflowQueueWithDMStats<>(feedStats);
+    } else {
+      feed = new OverflowQueueWithDMStats<>(feedSize, feedStats);
+    }
+    return feed;
+  }
+
+  public static ExecutorService newThreadPoolWithFeedStatistics(String threadName,
+      ThreadInitializer threadInitializer, CommandWrapper commandWrapper,
+      int poolSize, PoolStatHelper poolStats,
+      ThreadsMonitoring threadsMonitoring, int feedSize, QueueStatHelper feedStats) {
+    BlockingQueue<Runnable> feed = createFeedWithStatistics(feedSize, feedStats);
+    return newThreadPool(threadName, threadInitializer, commandWrapper, poolSize, poolStats,
+        threadsMonitoring, feed);
+  }
+
+  public static ExecutorService newThreadPool(String threadName,
+      ThreadInitializer threadInitializer, CommandWrapper commandWrapper,
+      int poolSize, PoolStatHelper poolStats,
+      ThreadsMonitoring threadsMonitoring, BlockingQueue<Runnable> feed) {
+    ThreadFactory threadFactory =
+        new LoggingThreadFactory(threadName, threadInitializer, commandWrapper);
+    return new PooledExecutorWithDMStats(feed, poolSize, poolStats, threadFactory,
+        threadsMonitoring);
+  }
+
+  public static ExecutorService newThreadPoolWithSynchronousFeed(String threadName,
+      CommandWrapper commandWrapper,
+      int poolSize) {
+    ThreadFactory threadFactory = new LoggingThreadFactory(threadName, commandWrapper);
+    SynchronousQueue<Runnable> feed = new SynchronousQueue<>();
+    return new PooledExecutorWithDMStats(feed, poolSize, threadFactory, null);
+  }
+
+  public static ExecutorService newThreadPoolWithSynchronousFeed(String threadName,
+      CommandWrapper commandWrapper,
+      int poolSize, PoolStatHelper poolStats, int msTimeout, ThreadsMonitoring threadsMonitoring) {
+    ThreadFactory threadFactory = new LoggingThreadFactory(threadName, commandWrapper);
+    SynchronousQueue<Runnable> feed = new SynchronousQueue<>();
+    return new PooledExecutorWithDMStats(feed, poolSize, poolStats, threadFactory, msTimeout,
+        threadsMonitoring);
+  }
+
+  public static ExecutorService newThreadPoolWithSynchronousFeed(String threadName, int poolSize,
+      PoolStatHelper stats, int msTimeout, RejectedExecutionHandler rejectionHandler) {
+    final SynchronousQueue<Runnable> feed = new SynchronousQueue<>();
+    ThreadFactory threadFactory = new LoggingThreadFactory(threadName);
+    return new PooledExecutorWithDMStats(feed, poolSize, stats, threadFactory, msTimeout,
+        rejectionHandler, null);
+  }
+
+  public static ExecutorService newThreadPoolWithSynchronousFeed(String threadName,
+      ThreadInitializer threadInitializer, CommandWrapper commandWrapper,
+      int corePoolSize, int maximumPoolSize, long keepAliveSeconds) {
+    final BlockingQueue<Runnable> blockingQueue = new SynchronousQueue<>();
+    ThreadFactory threadFactory =
+        new LoggingThreadFactory(threadName, threadInitializer, commandWrapper);
+    return new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 0, SECONDS, blockingQueue,
+        threadFactory);
+  }
+
+  public static ExecutorService newThreadPoolWithSynchronousFeed(String threadName,
+      int corePoolSize, int maximumPoolSize, long keepAliveSeconds) {
+    return newThreadPoolWithSynchronousFeed(threadName, null, null, corePoolSize, maximumPoolSize,
+        keepAliveSeconds);
+  }
+
+  public static ExecutorService newThreadPoolWithSynchronousFeedThatHandlesRejection(
+      String threadName,
+      ThreadInitializer threadInitializer, CommandWrapper commandWrapper,
+      int corePoolSize, int maximumPoolSize,
+      long keepAliveSeconds) {
+    final BlockingQueue<Runnable> blockingQueue = new SynchronousQueue<>();
+    final RejectedExecutionHandler rejectedExecutionHandler = (r, pool) -> {
+      try {
+        blockingQueue.put(r);
+      } catch (InterruptedException ex) {
+        Thread.currentThread().interrupt(); // preserve the state
+        throw new RejectedExecutionException("interrupted", ex);
+      }
+    };
+    ThreadFactory threadFactory =
+        new LoggingThreadFactory(threadName, threadInitializer, commandWrapper);
+    return new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveSeconds, SECONDS,
+        blockingQueue,
+        threadFactory, rejectedExecutionHandler);
+  }
+
+  private static ThreadPoolExecutor newFixedThreadPool(String threadName, boolean isDaemon,
+      int poolSize, long keepAliveSeconds, BlockingQueue<Runnable> feed) {
+    ThreadFactory threadFactory = new LoggingThreadFactory(threadName, isDaemon);
+    return new ThreadPoolExecutor(poolSize, poolSize,
+        keepAliveSeconds, SECONDS,
+        feed, threadFactory);
+  }
+
+  private static ThreadPoolExecutor newFixedThreadPool(String threadName, boolean isDaemon,
+      long keepAliveSeconds, int poolSize) {
+    LinkedBlockingQueue<Runnable> feed = new LinkedBlockingQueue<>();
+    return newFixedThreadPool(threadName, isDaemon, poolSize, keepAliveSeconds, feed);
+  }
+
+  public static ExecutorService newFixedThreadPool(String threadName, boolean isDaemon,
+      int poolSize) {
+    return newFixedThreadPool(threadName, isDaemon, 0L, poolSize);
+  }
+
+  public static ExecutorService newFixedThreadPoolWithTimeout(String threadName, int poolSize,
+      int keepAliveSeconds) {
+    return newFixedThreadPool(threadName, true, keepAliveSeconds, poolSize);
+  }
+
+  public static ExecutorService newFixedThreadPoolWithTimeout(String threadName,
+      int poolSize, int keepAliveSeconds, QueueStatHelper feedStats) {
+    BlockingQueue<Runnable> feed = createFeedWithStatistics(0, feedStats);
+    return newFixedThreadPool(threadName, true, poolSize, keepAliveSeconds, feed);
+  }
+
+  public static ExecutorService newFixedThreadPoolWithFeedSize(String threadName,
+      int poolSize, int feedSize) {
+    LinkedBlockingQueue<Runnable> feed = new LinkedBlockingQueue<>(feedSize);
+    RejectedExecutionHandler rejectionHandler = new ThreadPoolExecutor.CallerRunsPolicy();
+    ThreadFactory threadFactory = new LoggingThreadFactory(threadName);
+    ThreadPoolExecutor executor = new ThreadPoolExecutor(poolSize, poolSize, 10, SECONDS, feed,
+        threadFactory, rejectionHandler);
+    executor.allowCoreThreadTimeOut(true);
+    return executor;
+  }
+
+  public static ExecutorService newSingleThreadExecutor(String threadName, boolean isDaemon) {
+    ThreadFactory threadFactory = new LoggingThreadFactory(threadName, isDaemon);
+    return new ThreadPoolExecutor(1, 1, 0L, SECONDS,
+        new LinkedBlockingQueue<Runnable>(),
+        threadFactory);
+  }
+
+  public static ExecutorService newCachedThreadPool(String threadName, boolean isDaemon) {
+    ThreadFactory threadFactory = new LoggingThreadFactory(threadName, isDaemon);
+    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
+        60L, TimeUnit.SECONDS,
+        new SynchronousQueue<Runnable>(),
+        threadFactory);
+  }
+
+  public static ExecutorService newWorkStealingPool(String threadName, int maxParallelThreads) {
+    final ForkJoinWorkerThreadFactory factory = pool -> {
+      ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
+      LoggingUncaughtExceptionHandler.setOnThread(worker);
+      worker.setName(threadName + worker.getPoolIndex());
+      return worker;
+    };
+    return new ForkJoinPool(maxParallelThreads, factory, null, true);
+  }
+
+  public static Executor newThreadOnEachExecute(String threadName) {
+    return command -> new LoggingThread(threadName, command).start();
+  }
+
+  public static ScheduledExecutorService newScheduledThreadPool(String threadName, int poolSize) {
+    return newScheduledThreadPool(threadName, poolSize, true);
+  }
+
+  public static ScheduledExecutorService newScheduledThreadPool(String threadName, int poolSize,
+      boolean executeDelayedTasks) {
+    ScheduledThreadPoolExecutor result =
+        new ScheduledThreadPoolExecutor(poolSize, new LoggingThreadFactory(threadName));
+    result.setExecuteExistingDelayedTasksAfterShutdownPolicy(executeDelayedTasks);
+    return result;
+  }
+
+  public static ScheduledExecutorService newSingleThreadScheduledExecutor(String threadName) {
+    return newScheduledThreadPool(threadName, 1);
+  }
+
+  public static ScheduledExecutorService newScheduledThreadPool(String threadName, int poolSize,
+      int keepAliveMillis, ThreadsMonitoring threadsMonitoring) {
+    ScheduledThreadPoolExecutorWithKeepAlive result =
+        new ScheduledThreadPoolExecutorWithKeepAlive(poolSize,
+            keepAliveMillis, MILLISECONDS, new LoggingThreadFactory(threadName),
+            threadsMonitoring);
+    result.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
+    result.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
+    return result;
+  }
+
+  private LoggingExecutors() {
+    // no instances allowed
+  }
+
+}
diff --git a/geode-core/src/main/java/org/apache/geode/internal/logging/LoggingThread.java b/geode-core/src/main/java/org/apache/geode/internal/logging/LoggingThread.java
new file mode 100644
index 0000000..87b2d35
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/internal/logging/LoggingThread.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geode.internal.logging;
+
+/**
+ * LoggingThread instances always handle uncaught exceptions by logging them.
+ */
+public class LoggingThread extends Thread {
+
+  /**
+   * Creates a daemon thread with the given name
+   * that logs uncaught exceptions.
+   *
+   * @param name the name of the thread
+   */
+  public LoggingThread(String name) {
+    this(name, null);
+  }
+
+  /**
+   * Creates a daemon thread with the given name and runnable
+   * that logs uncaught exceptions.
+   *
+   * @param name the name of the thread
+   * @param runnable what the thread will run
+   */
+  public LoggingThread(String name, Runnable runnable) {
+    this(name, true, runnable);
+  }
+
+  /**
+   * Creates a thread with the given name and runnable
+   * that logs uncaught exceptions.
+   *
+   * @param name the name of the thread
+   * @param isDaemon true if thread will be marked as a daemon
+   * @param runnable what the thread will run
+   */
+  public LoggingThread(String name, boolean isDaemon, Runnable runnable) {
+    super(runnable, name);
+    setDaemon(isDaemon);
+    LoggingUncaughtExceptionHandler.setOnThread(this);
+  }
+}
diff --git a/geode-core/src/main/java/org/apache/geode/internal/logging/LoggingThreadFactory.java b/geode-core/src/main/java/org/apache/geode/internal/logging/LoggingThreadFactory.java
new file mode 100644
index 0000000..e90257d
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/internal/logging/LoggingThreadFactory.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geode.internal.logging;
+
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Threads produced by instances of this class will always
+ * log uncaught exceptions. They will also always be daemon
+ * and have unique names that contain the "baseName" passed
+ * to the constructor.
+ * <p>
+ * What happens each time a thread is created can be customized
+ * using the optional "threadInitializer".
+ * <p>
+ * What happens each time a thread is run can be customized
+ * using the optional "commandWrapper".
+ */
+public class LoggingThreadFactory implements ThreadFactory {
+
+  private final String baseName;
+  private final CommandWrapper commandWrapper;
+  private final ThreadInitializer threadInitializer;
+  private final boolean isDaemon;
+  private final AtomicInteger threadCount = new AtomicInteger(1);
+
+  public interface ThreadInitializer {
+    public void initialize(Thread thread);
+  }
+
+  public interface CommandWrapper {
+    /**
+     * Invoke the current method passing it a runnable that it can
+     * decide to call when it wants.
+     */
+    public void invoke(Runnable runnable);
+  }
+
+  /**
+   * Create a factory that produces daemon threads that log uncaught exceptions
+   *
+   * @param baseName the base name will be included in every thread name
+   * @param threadInitializer if not null, will be invoked with the thread each time a thread is
+   *        created
+   * @param commandWrapper if not null, will be invoked by each thread created by this factory
+   * @param isDaemon true if threads will be daemons
+   */
+  public LoggingThreadFactory(String baseName, ThreadInitializer threadInitializer,
+      CommandWrapper commandWrapper, boolean isDaemon) {
+    this.baseName = baseName;
+    this.threadInitializer = threadInitializer;
+    this.commandWrapper = commandWrapper;
+    this.isDaemon = isDaemon;
+  }
+
+  /**
+   * Create a factory that produces daemon threads that log uncaught exceptions
+   *
+   * @param baseName the base name will be included in every thread name
+   * @param threadInitializer if not null, will be invoked with the thread each time a thread is
+   *        created
+   * @param commandWrapper if not null, will be invoked by each thread created by this factory
+   */
+  public LoggingThreadFactory(String baseName, ThreadInitializer threadInitializer,
+      CommandWrapper commandWrapper) {
+    this(baseName, threadInitializer, commandWrapper, true);
+  }
+
+  /**
+   * Create a factory that produces daemon threads that log uncaught exceptions
+   *
+   * @param baseName the base name will be included in every thread name
+   */
+  public LoggingThreadFactory(String baseName) {
+    this(baseName, null, null, true);
+  }
+
+  /**
+   * Create a factory that produces threads that log uncaught exceptions
+   *
+   * @param baseName the base name will be included in every thread name
+   * @param isDaemon true if threads will be daemons
+   */
+  public LoggingThreadFactory(String baseName, boolean isDaemon) {
+    this(baseName, null, null, isDaemon);
+  }
+
+  /**
+   * Create a factory that produces daemon threads that log uncaught exceptions
+   *
+   * @param baseName the base name will be included in every thread name
+   * @param commandWrapper if not null, will be invoked by each thread created by this factory
+   */
+  public LoggingThreadFactory(String baseName, CommandWrapper commandWrapper) {
+    this(baseName, null, commandWrapper, true);
+  }
+
+  private String getUniqueName() {
+    return this.baseName + threadCount.getAndIncrement();
+  }
+
+  @Override
+  public Thread newThread(Runnable runnable) {
+    Runnable commandToRun;
+    if (this.commandWrapper != null) {
+      commandToRun = () -> this.commandWrapper.invoke(runnable);
+    } else {
+      commandToRun = runnable;
+    }
+    Thread thread = new LoggingThread(getUniqueName(), isDaemon, commandToRun);
+    if (this.threadInitializer != null) {
+      this.threadInitializer.initialize(thread);
+    }
+    return thread;
+  }
+}
diff --git a/geode-core/src/main/java/org/apache/geode/internal/logging/LoggingThreadGroup.java b/geode-core/src/main/java/org/apache/geode/internal/logging/LoggingThreadGroup.java
deleted file mode 100755
index f3ae92d..0000000
--- a/geode-core/src/main/java/org/apache/geode/internal/logging/LoggingThreadGroup.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
- * agreements. See the NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance with the License. You may obtain a
- * copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package org.apache.geode.internal.logging;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-
-import org.apache.logging.log4j.Logger;
-
-import org.apache.geode.SystemFailure;
-import org.apache.geode.distributed.internal.InternalDistributedSystem;
-import org.apache.geode.i18n.StringId;
-import org.apache.geode.internal.Assert;
-import org.apache.geode.internal.i18n.LocalizedStrings;
-import org.apache.geode.internal.logging.log4j.LocalizedMessage;
-
-/**
- * A <code>ThreadGroup</code> that logs all {@linkplain #uncaughtException uncaught exceptions} to a
- * GemFire <code>LogWriterI18n</code>. It also keeps track of the uncaught exceptions that were
- * thrown by its threads. This is comes in handy when a thread fails to initialize properly (see bug
- * 32550).
- *
- * @see LoggingThreadGroup#createThreadGroup
- *
- * @since GemFire 4.0
- */
-public class LoggingThreadGroup extends ThreadGroup {
-
-  /** A "local" log writer that logs exceptions to standard error */
-  private static final StandardErrorPrinter stderr =
-      new StandardErrorPrinter(InternalLogWriter.ALL_LEVEL);
-
-  /** A set of all created LoggingThreadGroups */
-  private static final Collection<LoggingThreadGroup> loggingThreadGroups =
-      new ArrayList<LoggingThreadGroup>();
-
-  /**
-   * Returns a <code>ThreadGroup</code> whose {@link ThreadGroup#uncaughtException} method logs to
-   * both {#link System#err} and the given <code>InternalLogWriter</code>.
-   *
-   * @param name The name of the <code>ThreadGroup</code>
-   */
-  public static LoggingThreadGroup createThreadGroup(final String name) {
-    return createThreadGroup(name, (Logger) null);
-  }
-
-  /**
-   * Returns a <code>ThreadGroup</code> whose {@link ThreadGroup#uncaughtException} method logs to
-   * both {#link System#err} and the given <code>InternalLogWriter</code>.
-   *
-   * @param name The name of the <code>ThreadGroup</code>
-   * @param logWriter A <code>InternalLogWriter</code> to log uncaught exceptions to. It is okay for
-   *        this argument to be <code>null</code>.
-   *
-   *        author David Whitlock
-   * @since GemFire 3.0
-   */
-  public static LoggingThreadGroup createThreadGroup(final String name,
-      final InternalLogWriter logWriter) {
-    // Cache the LoggingThreadGroups so that we don't create a
-    // gazillion of them.
-    LoggingThreadGroup group = null;
-    synchronized (loggingThreadGroups) {
-      for (Iterator<LoggingThreadGroup> iter = loggingThreadGroups.iterator(); iter.hasNext();) {
-
-        LoggingThreadGroup group2 = (LoggingThreadGroup) iter.next();
-        if (group2.isDestroyed()) {
-          // Clean is this iterator out
-          iter.remove();
-          continue;
-        }
-
-        if (name.equals(group2.getName())) {
-          // We already have one!
-          // Change the underlying logger to point to new one (creating new
-          // thread groups for different loggers leaks groups for repeated
-          // connect/disconnects as in dunits for example)
-          if (logWriter != group2.logWriter) {
-            group2.logWriter = logWriter;
-          }
-          group = group2;
-          break;
-        }
-      }
-
-      if (group == null) {
-        group = new LoggingThreadGroup(name, logWriter);
-        // force autoclean to false and not inherit from parent group
-        group.setDaemon(false);
-        loggingThreadGroups.add(group);
-      }
-    }
-
-    Assert.assertTrue(!group.isDestroyed());
-    return group;
-  }
-
-  /**
-   * Returns a <code>ThreadGroup</code> whose {@link ThreadGroup#uncaughtException} method logs to
-   * both {#link System#err} and the given <code>InternalLogWriter</code>.
-   *
-   * @param name The name of the <code>ThreadGroup</code>
-   * @param logger A <code>InternalLogWriter</code> to log uncaught exceptions to. It is okay for
-   *        this argument to be <code>null</code>.
-   *
-   *        author David Whitlock
-   * @since GemFire 3.0
-   */
-  public static LoggingThreadGroup createThreadGroup(final String name, final Logger logger) {
-    // Cache the LoggingThreadGroups so that we don't create a
-    // gazillion of them.
-    LoggingThreadGroup group = null;
-    synchronized (loggingThreadGroups) {
-      for (Iterator<LoggingThreadGroup> iter = loggingThreadGroups.iterator(); iter.hasNext();) {
-
-        LoggingThreadGroup group2 = (LoggingThreadGroup) iter.next();
-        if (group2.isDestroyed()) {
-          // Clean is this iterator out
-          iter.remove();
-          continue;
-        }
-
-        if (name.equals(group2.getName())) {
-          // We already have one!
-          // Change the underlying logger to point to new one (creating new
-          // thread groups for different loggers leaks groups for repeated
-          // connect/disconnects as in dunits for example)
-          if (logger != group2.logger) {
-            group2.logger = logger;
-          }
-          group = group2;
-          break;
-        }
-      }
-
-      if (group == null) {
-        group = new LoggingThreadGroup(name, logger);
-        // force autoclean to false and not inherit from parent group
-        group.setDaemon(false);
-        loggingThreadGroups.add(group);
-      }
-    }
-
-    Assert.assertTrue(!group.isDestroyed());
-    return group;
-  }
-
-  // /**
-  // * @deprecated Only for use by hydra for backwards compatability reasons.
-  // * Returns a <code>ThreadGroup</code> whose {@link
-  // * ThreadGroup#uncaughtException} method logs to both {#link
-  // * System#err} and the given <code>LogWriterI18n</code>.
-  // *
-  // * @param name
-  // * The name of the <code>ThreadGroup</code>
-  // * @param logger
-  // * A <code>LogWriter</code> to log uncaught exceptions to. It
-  // * is okay for this argument to be <code>null</code>.
-  // *
-  // * author kbanks
-  // * @since GemFire 6.0
-  // */
-  // @Deprecated public static LoggingThreadGroup createThreadGroup(final String name,
-  // final LogWriter logger) {
-  // return createThreadGroup(name,
-  // logger != null ? logger.convertToLogWriterI18n() : null);
-  // }
-
-  public static void cleanUpThreadGroups() {
-    synchronized (loggingThreadGroups) {
-      LoggingThreadGroup group;
-      Iterator<?> itr = loggingThreadGroups.iterator();
-      while (itr.hasNext()) {
-        group = (LoggingThreadGroup) itr.next();
-        if (!group.getName().equals(InternalDistributedSystem.SHUTDOWN_HOOK_NAME)
-            && !group.getName().equals("GemFireConnectionFactory Shutdown Hook")) {
-          group.cleanup();
-        }
-      }
-    }
-  }
-
-  /**
-   * Note: Must be used for test purposes ONLY.
-   *
-   * @return thread group with given name.
-   */
-  public static ThreadGroup getThreadGroup(final String threadGroupName) {
-    synchronized (loggingThreadGroups) {
-      for (Object object : loggingThreadGroups) {
-        LoggingThreadGroup threadGroup = (LoggingThreadGroup) object;
-        if (threadGroup.getName().equals(threadGroupName)) {
-          return threadGroup;
-        }
-      }
-      return null;
-    }
-  }
-
-  /**
-   * A log writer that the user has specified for logging uncaught exceptions.
-   */
-  protected volatile InternalLogWriter logWriter;
-
-  /**
-   * A logger that the user has specified for logging uncaught exceptions.
-   */
-  protected volatile Logger logger;
-
-  /**
-   * The count uncaught exceptions that were thrown by threads in this thread group.
-   */
-  private long uncaughtExceptionsCount;
-
-  /**
-   * Creates a new <code>LoggingThreadGroup</code> that logs uncaught exceptions to the given log
-   * writer.
-   *
-   * @param name The name of the thread group
-   * @param logWriter A logWriter to which uncaught exceptions are logged. May be <code>null</code>.
-   */
-  LoggingThreadGroup(final String name, final InternalLogWriter logWriter) {
-    super(name);
-    this.logWriter = logWriter;
-  }
-
-  /**
-   * Creates a new <code>LoggingThreadGroup</code> that logs uncaught exceptions to the given
-   * logger.
-   *
-   * @param name The name of the thread group
-   * @param logger A logger to which uncaught exceptions are logged. May be <code>null</code>.
-   */
-  LoggingThreadGroup(final String name, final Logger logger) {
-    super(name);
-    this.logger = logger;
-  }
-
-  private Object dispatchLock = new Object();
-
-  /**
-   * Logs an uncaught exception to a log writer
-   */
-  @Override
-  public void uncaughtException(final Thread t, final Throwable ex) {
-    synchronized (this.dispatchLock) {
-      if (ex instanceof VirtualMachineError) {
-        SystemFailure.setFailure((VirtualMachineError) ex); // don't throw
-      }
-      // Solution to treat the shutdown hook error as a special case.
-      // Do not change the hook's thread name without also changing it here.
-      String threadName = t.getName();
-      if ((ex instanceof NoClassDefFoundError)
-          && (threadName.equals(InternalDistributedSystem.SHUTDOWN_HOOK_NAME))) {
-        final StringId msg =
-            LocalizedStrings.UNCAUGHT_EXCEPTION_IN_THREAD_0_THIS_MESSAGE_CAN_BE_DISREGARDED_IF_IT_OCCURRED_DURING_AN_APPLICATION_SERVER_SHUTDOWN_THE_EXCEPTION_MESSAGE_WAS_1;
-        final Object[] msgArgs = new Object[] {t, ex.getLocalizedMessage()};
-        stderr.info(msg, msgArgs);
-        if (this.logger != null) {
-          this.logger.info(LocalizedMessage.create(msg, msgArgs));
-        }
-        if (this.logWriter != null) {
-          this.logWriter.info(msg, msgArgs);
-        }
-      } else {
-        stderr.severe(LocalizedStrings.UNCAUGHT_EXCEPTION_IN_THREAD_0, t, ex);
-        if (this.logger != null) {
-          this.logger.fatal(
-              LocalizedMessage.create(LocalizedStrings.UNCAUGHT_EXCEPTION_IN_THREAD_0, t), ex);
-        }
-        if (this.logWriter != null) {
-          this.logWriter.severe(LocalizedStrings.UNCAUGHT_EXCEPTION_IN_THREAD_0, t, ex);
-        }
-      }
-      // if (!(ex instanceof RuntimeException) && (ex instanceof Exception)) {
-      // something's fishy - checked exceptions shouldn't get here
-      // this.logger.severe("stack trace showing origin of uncaught checked exception", new
-      // Exception("stack trace"));
-      // }
-      this.uncaughtExceptionsCount++;
-    }
-  }
-
-
-  /**
-   * clear number of uncaught exceptions
-   */
-  public void clearUncaughtExceptionsCount() {
-    synchronized (this.dispatchLock) {
-      this.uncaughtExceptionsCount = 0;
-    }
-  }
-
-  /**
-   * Returns the number of uncaught exceptions that occurred in threads in this thread group.
-   */
-  public long getUncaughtExceptionsCount() {
-    synchronized (this.dispatchLock) {
-      return uncaughtExceptionsCount;
-    }
-  }
-
-  /**
-   * clean up the threadgroup, releasing resources that could be problematic (bug 35388)
-   *
-   * @since GemFire 4.2.3
-   */
-  public synchronized void cleanup() {
-    // the logwriter holds onto a distribution config, which holds onto
-    // the InternalDistributedSystem, which holds onto the
-    // DistributionManager, which holds onto ... you get the idea
-    this.logger = null;
-    this.logWriter = null;
-  }
-}
diff --git a/geode-core/src/main/java/org/apache/geode/internal/logging/LoggingUncaughtExceptionHandler.java b/geode-core/src/main/java/org/apache/geode/internal/logging/LoggingUncaughtExceptionHandler.java
new file mode 100644
index 0000000..a132a79
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/internal/logging/LoggingUncaughtExceptionHandler.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geode.internal.logging;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.text.MessageFormat;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.logging.log4j.Logger;
+
+import org.apache.geode.SystemFailure;
+import org.apache.geode.distributed.internal.InternalDistributedSystem;
+
+/**
+ * This class delegates to a static singleton that handles all
+ * exceptions not caught by any thread created in geode.
+ * So all interactions with it are done with static methods.
+ */
+public class LoggingUncaughtExceptionHandler {
+  private static final Implementation handler =
+      new Implementation(LogService.getLogger(), error -> SystemFailure.setFailure(error));
+
+  public static UncaughtExceptionHandler getInstance() {
+    return handler;
+  }
+
+  /**
+   * Sets the logging uncaught exception handler on the given thread.
+   */
+  public static void setOnThread(Thread thread) {
+    handler.setOnThread(thread);
+  }
+
+  public static int getUncaughtExceptionsCount() {
+    return handler.getUncaughtExceptionsCount();
+  }
+
+  public static void clearUncaughtExceptionsCount() {
+    handler.clearUncaughtExceptionsCount();
+  }
+
+  LoggingUncaughtExceptionHandler() {
+    // no instances allowed
+  }
+
+  // non-private for unit testing
+  interface FailureSettor {
+    void setFailure(VirtualMachineError error);
+  }
+
+  // non-private for unit testing
+  static class Implementation implements UncaughtExceptionHandler {
+    private final Logger logger;
+    private final FailureSettor failureSettor;
+    private final AtomicInteger uncaughtExceptionsCount = new AtomicInteger();
+
+    Implementation(Logger logger, FailureSettor failureSettor) {
+      this.logger = logger;
+      this.failureSettor = failureSettor;
+    }
+
+    @Override
+    public void uncaughtException(Thread t, Throwable ex) {
+      if (ex instanceof VirtualMachineError) {
+        this.failureSettor.setFailure((VirtualMachineError) ex);
+      }
+      // Solution to treat the shutdown hook error as a special case.
+      // Do not change the hook's thread name without also changing it here.
+      if ((ex instanceof NoClassDefFoundError)
+          && (t.getName().equals(InternalDistributedSystem.SHUTDOWN_HOOK_NAME))) {
+        logger.info(
+            "Uncaught exception in thread {0} this message can be disregarded if it occurred during an Application Server shutdown. The Exception message was: {1}",
+            t, ex);
+      } else {
+        String message = MessageFormat.format("Uncaught exception in thread {0}", t);
+        logger.fatal(message, ex);
+      }
+      uncaughtExceptionsCount.incrementAndGet();
+    }
+
+    void setOnThread(Thread thread) {
+      thread.setUncaughtExceptionHandler(this);
+    }
+
+    int getUncaughtExceptionsCount() {
+      return uncaughtExceptionsCount.get();
+    }
+
+    void clearUncaughtExceptionsCount() {
+      uncaughtExceptionsCount.set(0);
+    }
+  }
+}
diff --git a/geode-core/src/main/java/org/apache/geode/internal/memcached/commands/StorageCommand.java b/geode-core/src/main/java/org/apache/geode/internal/memcached/commands/StorageCommand.java
index 8ea413d..ee2f05f 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/memcached/commands/StorageCommand.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/memcached/commands/StorageCommand.java
@@ -23,10 +23,10 @@ import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.geode.cache.Cache;
+import org.apache.geode.internal.logging.LoggingThreadFactory;
 import org.apache.geode.internal.memcached.KeyWrapper;
 import org.apache.geode.internal.memcached.RequestReader;
 import org.apache.geode.memcached.GemFireMemcachedServer.Protocol;
@@ -49,15 +49,7 @@ public abstract class StorageCommand extends AbstractCommand {
    * thread pool for scheduling expiration tasks
    */
   private static ScheduledExecutorService expiryExecutor =
-      new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
-        @Override
-        public Thread newThread(Runnable r) {
-          Thread t = new Thread(r);
-          t.setName("memcached-expiryExecutor");
-          t.setDaemon(true);
-          return t;
-        }
-      });
+      new ScheduledThreadPoolExecutor(1, new LoggingThreadFactory("memcached-expiryExecutor"));
 
   private static ConcurrentMap<Object, ScheduledFuture> expiryFutures =
       new ConcurrentHashMap<Object, ScheduledFuture>();
diff --git a/geode-core/src/main/java/org/apache/geode/internal/net/SocketCloser.java b/geode-core/src/main/java/org/apache/geode/internal/net/SocketCloser.java
index 55d1e09..2aeca71 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/net/SocketCloser.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/net/SocketCloser.java
@@ -19,11 +19,7 @@ import java.net.Socket;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
@@ -31,7 +27,7 @@ import org.apache.logging.log4j.Logger;
 
 import org.apache.geode.SystemFailure;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
 
 /**
  * This class allows sockets to be closed without blocking. In some cases we have seen a call of
@@ -120,27 +116,7 @@ public class SocketCloser {
   }
 
   private ExecutorService getWorkStealingPool(int maxParallelThreads) {
-    return Executors.newWorkStealingPool(maxParallelThreads);
-  }
-
-  /**
-   * @deprecated since GEODE 1.3.0. Use @link{getWorkStealingPool}
-   */
-  @Deprecated
-  private ExecutorService createThreadPoolExecutor() {
-    final ThreadGroup threadGroup =
-        LoggingThreadGroup.createThreadGroup("Socket asyncClose", logger);
-    ThreadFactory threadFactory = new ThreadFactory() {
-      public Thread newThread(final Runnable command) {
-        Thread thread = new Thread(threadGroup, command);
-        thread.setDaemon(true);
-        return thread;
-      }
-    };
-
-    return new ThreadPoolExecutor(asyncClosePoolMaxThreads, asyncClosePoolMaxThreads,
-        asyncClosePoolKeepAliveSeconds, TimeUnit.SECONDS, new LinkedBlockingQueue<>(),
-        threadFactory);
+    return LoggingExecutors.newWorkStealingPool("SocketCloser-", maxParallelThreads);
   }
 
   /**
diff --git a/geode-core/src/main/java/org/apache/geode/internal/offheap/DisconnectingOutOfOffHeapMemoryListener.java b/geode-core/src/main/java/org/apache/geode/internal/offheap/DisconnectingOutOfOffHeapMemoryListener.java
index 67614b2..e2b8ed5 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/offheap/DisconnectingOutOfOffHeapMemoryListener.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/offheap/DisconnectingOutOfOffHeapMemoryListener.java
@@ -16,6 +16,7 @@ package org.apache.geode.internal.offheap;
 
 import org.apache.geode.OutOfOffHeapMemoryException;
 import org.apache.geode.distributed.internal.InternalDistributedSystem;
+import org.apache.geode.internal.logging.LoggingThread;
 
 /**
  * Used to asynchronously disconnect an InternalDistributedSystem when we run out of off-heap
@@ -65,13 +66,9 @@ class DisconnectingOutOfOffHeapMemoryListener implements OutOfOffHeapMemoryListe
       // invoking disconnect is async because caller may be a DM pool thread which will block until
       // DM shutdown times out
 
-      // LogWriterImpl.LoggingThreadGroup group = LogWriterImpl.createThreadGroup("MemScale
-      // Threads", ids.getLogWriterI18n());
       String name = this.getClass().getSimpleName() + "@" + this.hashCode()
           + " Handle OutOfOffHeapMemoryException Thread";
-      // Thread thread = new Thread(group, runnable, name);
-      Thread thread = new Thread(runnable, name);
-      thread.setDaemon(true);
+      Thread thread = new LoggingThread(name, runnable);
       thread.start();
     }
   }
diff --git a/geode-core/src/main/java/org/apache/geode/internal/process/ControlFileWatchdog.java b/geode-core/src/main/java/org/apache/geode/internal/process/ControlFileWatchdog.java
index 14decbc..0dd60f3 100755
--- a/geode-core/src/main/java/org/apache/geode/internal/process/ControlFileWatchdog.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/process/ControlFileWatchdog.java
@@ -23,6 +23,7 @@ import java.io.IOException;
 import org.apache.logging.log4j.Logger;
 
 import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.logging.LoggingThread;
 
 /**
  * Invokes a ControlRequestHandler when a control file has been created.
@@ -106,8 +107,7 @@ class ControlFileWatchdog implements Runnable {
   void start() {
     synchronized (this) {
       if (thread == null) {
-        thread = new Thread(this, createThreadName());
-        thread.setDaemon(true);
+        thread = new LoggingThread(createThreadName(), this);
         alive = true;
         thread.start();
       }
diff --git a/geode-core/src/main/java/org/apache/geode/internal/process/ProcessStreamReader.java b/geode-core/src/main/java/org/apache/geode/internal/process/ProcessStreamReader.java
index f68e6fb..f3c817a 100755
--- a/geode-core/src/main/java/org/apache/geode/internal/process/ProcessStreamReader.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/process/ProcessStreamReader.java
@@ -21,6 +21,8 @@ import java.io.InputStream;
 
 import org.apache.commons.lang.SystemUtils;
 
+import org.apache.geode.internal.logging.LoggingThread;
+
 /**
  * Reads the output stream of a Process.
  *
@@ -62,8 +64,7 @@ public abstract class ProcessStreamReader implements Runnable {
   public ProcessStreamReader start() {
     synchronized (this) {
       if (thread == null) {
-        thread = new Thread(this, createThreadName());
-        thread.setDaemon(true);
+        thread = new LoggingThread(createThreadName(), this);
         thread.start();
       } else if (thread.isAlive()) {
         throw new IllegalStateException(this + " has already started");
@@ -95,8 +96,7 @@ public abstract class ProcessStreamReader implements Runnable {
 
     String threadName =
         getClass().getSimpleName() + " stopAfterDelay Thread @" + Integer.toHexString(hashCode());
-    Thread thread = new Thread(delayedStop, threadName);
-    thread.setDaemon(true);
+    Thread thread = new LoggingThread(threadName, delayedStop);
     thread.start();
     return this;
   }
diff --git a/geode-core/src/main/java/org/apache/geode/internal/statistics/CallbackSampler.java b/geode-core/src/main/java/org/apache/geode/internal/statistics/CallbackSampler.java
index 0e51c92..f50c30e 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/statistics/CallbackSampler.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/statistics/CallbackSampler.java
@@ -14,7 +14,6 @@
  */
 package org.apache.geode.internal.statistics;
 
-import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
@@ -24,6 +23,7 @@ import org.apache.geode.CancelCriterion;
 import org.apache.geode.Statistics;
 import org.apache.geode.SystemFailure;
 import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.logging.LoggingExecutors;
 
 public class CallbackSampler {
   private static final Logger logger = LogService.getLogger();
@@ -39,13 +39,9 @@ public class CallbackSampler {
     this.statSamplerStats = statSamplerStats;
   }
 
-  public void start(StatisticsManager statisticsManager, ThreadGroup threadGroup,
-      int sampleInterval, TimeUnit timeUnit) {
-    ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(runnable -> {
-      Thread thread = new Thread(threadGroup, runnable, "CallbackSampler");
-      thread.setDaemon(true);
-      return thread;
-    });
+  public void start(StatisticsManager statisticsManager, int sampleInterval, TimeUnit timeUnit) {
+    ScheduledExecutorService executor =
+        LoggingExecutors.newSingleThreadScheduledExecutor("CallbackSampler");
     start(executor, statisticsManager, sampleInterval, timeUnit);
   }
 
diff --git a/geode-core/src/main/java/org/apache/geode/internal/statistics/HostStatSampler.java b/geode-core/src/main/java/org/apache/geode/internal/statistics/HostStatSampler.java
index 14e3c29..6ce10d7 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/statistics/HostStatSampler.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/statistics/HostStatSampler.java
@@ -30,7 +30,7 @@ import org.apache.geode.internal.NanoTimer;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.io.MainWithChildrenRollingFileHandler;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.logging.log4j.LogMarker;
 import org.apache.geode.internal.net.SocketCreator;
@@ -289,14 +289,9 @@ public abstract class HostStatSampler
                   .toLocalizedString());
         }
       }
-      ThreadGroup group = LoggingThreadGroup.createThreadGroup("StatSampler Threads");
-
-      this.callbackSampler.start(getStatisticsManager(), group, getSampleRate(),
-          TimeUnit.MILLISECONDS);
-      statThread = new Thread(group, this);
-      statThread.setName(statThread.getName() + " StatSampler");
+      this.callbackSampler.start(getStatisticsManager(), getSampleRate(), TimeUnit.MILLISECONDS);
+      statThread = new LoggingThread("StatSampler", this);
       statThread.setPriority(Thread.MAX_PRIORITY);
-      statThread.setDaemon(true);
       statThread.start();
       // fix #46310 (race between management and sampler init) by waiting for init here
       try {
diff --git a/geode-core/src/main/java/org/apache/geode/internal/statistics/StatMonitorHandler.java b/geode-core/src/main/java/org/apache/geode/internal/statistics/StatMonitorHandler.java
index 9516b18..05bc848 100755
--- a/geode-core/src/main/java/org/apache/geode/internal/statistics/StatMonitorHandler.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/statistics/StatMonitorHandler.java
@@ -23,6 +23,7 @@ import org.apache.geode.SystemFailure;
 import org.apache.geode.distributed.internal.DistributionConfig;
 import org.apache.geode.internal.concurrent.ConcurrentHashSet;
 import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LogMarker;
 
 /**
@@ -248,8 +249,7 @@ public class StatMonitorHandler implements SampleHandler {
     void start() {
       synchronized (this) {
         if (this.consumer == null) {
-          this.consumer = new Thread(this, toString());
-          this.consumer.setDaemon(true);
+          this.consumer = new LoggingThread(toString(), this);
           this.alive = true;
           this.consumer.start();
         }
diff --git a/geode-core/src/main/java/org/apache/geode/internal/tcp/Connection.java b/geode-core/src/main/java/org/apache/geode/internal/tcp/Connection.java
index 71585d5..6a035d2 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/tcp/Connection.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/tcp/Connection.java
@@ -78,7 +78,7 @@ import org.apache.geode.internal.SystemTimer.SystemTimerTask;
 import org.apache.geode.internal.Version;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.AlertAppender;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.net.SocketCreator;
@@ -2864,13 +2864,8 @@ public class Connection implements Runnable {
         }
       }
       this.asyncQueuingInProgress = true;
-      ThreadGroup group = LoggingThreadGroup.createThreadGroup("P2P Writer Threads", logger);
-      this.pusherThread = new Thread(group, new Runnable() {
-        public void run() {
-          Connection.this.runNioPusher();
-        }
-      }, "P2P async pusher to " + this.remoteAddr);
-      this.pusherThread.setDaemon(true);
+      this.pusherThread =
+          new LoggingThread("P2P async pusher to " + this.remoteAddr, this::runNioPusher);
     } // synchronized
     this.pusherThread.start();
   }
diff --git a/geode-core/src/main/java/org/apache/geode/internal/tcp/ConnectionTable.java b/geode-core/src/main/java/org/apache/geode/internal/tcp/ConnectionTable.java
index 89d7693..d2dd563 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/tcp/ConnectionTable.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/tcp/ConnectionTable.java
@@ -26,15 +26,10 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.SynchronousQueue;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.logging.log4j.Logger;
@@ -51,7 +46,7 @@ import org.apache.geode.internal.Assert;
 import org.apache.geode.internal.SystemTimer;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.internal.logging.log4j.AlertAppender;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.net.SocketCloser;
@@ -214,31 +209,12 @@ public class ConnectionTable {
   }
 
   private Executor createThreadPoolForIO(boolean conserveSockets) {
-    Executor executor = null;
-    final ThreadGroup connectionRWGroup =
-        LoggingThreadGroup.createThreadGroup("P2P Reader Threads", logger);
     if (conserveSockets) {
-      executor = new Executor() {
-        @Override
-        public void execute(Runnable command) {
-          Thread th = new Thread(connectionRWGroup, command);
-          th.setDaemon(true);
-          th.start();
-        }
-      };
+      return LoggingExecutors.newThreadOnEachExecute("SharedP2PReader");
     } else {
-      BlockingQueue synchronousQueue = new SynchronousQueue();
-      ThreadFactory tf = new ThreadFactory() {
-        public Thread newThread(final Runnable command) {
-          Thread thread = new Thread(connectionRWGroup, command);
-          thread.setDaemon(true);
-          return thread;
-        }
-      };
-      executor = new ThreadPoolExecutor(1, Integer.MAX_VALUE, READER_POOL_KEEP_ALIVE_TIME,
-          TimeUnit.SECONDS, synchronousQueue, tf);
+      return LoggingExecutors.newThreadPoolWithSynchronousFeed("UnsharedP2PReader", 1,
+          Integer.MAX_VALUE, READER_POOL_KEEP_ALIVE_TIME);
     }
-    return executor;
   }
 
   /** conduit sends connected() after establishing the server socket */
diff --git a/geode-core/src/main/java/org/apache/geode/internal/tcp/TCPConduit.java b/geode-core/src/main/java/org/apache/geode/internal/tcp/TCPConduit.java
index 6135c07..23ddbad 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/tcp/TCPConduit.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/tcp/TCPConduit.java
@@ -27,13 +27,9 @@ import java.nio.channels.ServerSocketChannel;
 import java.nio.channels.SocketChannel;
 import java.util.Map;
 import java.util.Properties;
-import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.RejectedExecutionHandler;
-import java.util.concurrent.SynchronousQueue;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
 
 import javax.net.ssl.SSLException;
 
@@ -54,7 +50,8 @@ import org.apache.geode.distributed.internal.membership.InternalDistributedMembe
 import org.apache.geode.distributed.internal.membership.MembershipManager;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.AlertAppender;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.logging.log4j.LogMarker;
@@ -342,7 +339,7 @@ public class TCPConduit implements Runnable {
     }
   }
 
-  private ThreadPoolExecutor hsPool;
+  private ExecutorService hsPool;
 
   /**
    * the reason for a shutdown, if abnormal
@@ -358,8 +355,11 @@ public class TCPConduit implements Runnable {
    * added to fix bug 40436
    */
   public void setMaximumHandshakePoolSize(int maxSize) {
-    if (this.hsPool != null && maxSize > HANDSHAKE_POOL_SIZE) {
-      this.hsPool.setMaximumPoolSize(maxSize);
+    if (this.hsPool != null) {
+      ThreadPoolExecutor handshakePool = (ThreadPoolExecutor) this.hsPool;
+      if (maxSize > handshakePool.getMaximumPoolSize()) {
+        handshakePool.setMaximumPoolSize(maxSize);
+      }
     }
   }
 
@@ -372,37 +372,12 @@ public class TCPConduit implements Runnable {
     InetAddress ba = this.address;
 
     {
-      ThreadPoolExecutor tmp_hsPool = null;
-      String gName = "P2P-Handshaker " + ba + ":" + p;
-      final ThreadGroup socketThreadGroup = LoggingThreadGroup.createThreadGroup(gName, logger);
-
-      ThreadFactory socketThreadFactory = new ThreadFactory() {
-        int connNum = -1;
-
-        public Thread newThread(Runnable command) {
-          int tnum;
-          synchronized (this) {
-            tnum = ++connNum;
-          }
-          String tName = socketThreadGroup.getName() + " Thread " + tnum;
-          return new Thread(socketThreadGroup, command, tName);
-        }
-      };
+      ExecutorService tmp_hsPool = null;
+      String threadName = "P2P-Handshaker " + ba + ":" + p + " Thread ";
       try {
-        final BlockingQueue bq = new SynchronousQueue();
-        final RejectedExecutionHandler reh = new RejectedExecutionHandler() {
-          public void rejectedExecution(Runnable r, ThreadPoolExecutor pool) {
-            try {
-              bq.put(r);
-            } catch (InterruptedException ex) {
-              Thread.currentThread().interrupt(); // preserve the state
-              throw new RejectedExecutionException(
-                  LocalizedStrings.TCPConduit_INTERRUPTED.toLocalizedString(), ex);
-            }
-          }
-        };
-        tmp_hsPool = new ThreadPoolExecutor(1, HANDSHAKE_POOL_SIZE, HANDSHAKE_POOL_KEEP_ALIVE_TIME,
-            TimeUnit.SECONDS, bq, socketThreadFactory, reh);
+        tmp_hsPool =
+            LoggingExecutors.newThreadPoolWithSynchronousFeedThatHandlesRejection(threadName, null,
+                null, 1, HANDSHAKE_POOL_SIZE, HANDSHAKE_POOL_KEEP_ALIVE_TIME);
       } catch (IllegalArgumentException poolInitException) {
         throw new ConnectionException(
             LocalizedStrings.TCPConduit_WHILE_CREATING_HANDSHAKE_POOL.toLocalizedString(),
@@ -416,11 +391,9 @@ public class TCPConduit implements Runnable {
 
       id = new InetSocketAddress(socket.getInetAddress(), localPort);
       stopped = false;
-      ThreadGroup group = LoggingThreadGroup.createThreadGroup("P2P Listener Threads", logger);
-      thread = new Thread(group, this, "P2P Listener Thread " + id);
-      thread.setDaemon(true);
+      thread = new LoggingThread("P2P Listener Thread " + id, this);
       try {
-        thread.setPriority(thread.getThreadGroup().getMaxPriority());
+        thread.setPriority(Thread.MAX_PRIORITY);
       } catch (Exception e) {
         logger.info(LocalizedMessage.create(
             LocalizedStrings.TCPConduit_UNABLE_TO_SET_LISTENER_PRIORITY__0, e.getMessage()));
diff --git a/geode-core/src/main/java/org/apache/geode/internal/util/concurrent/CustomEntryConcurrentHashMap.java b/geode-core/src/main/java/org/apache/geode/internal/util/concurrent/CustomEntryConcurrentHashMap.java
index 9edb789..e65db3d 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/util/concurrent/CustomEntryConcurrentHashMap.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/util/concurrent/CustomEntryConcurrentHashMap.java
@@ -62,6 +62,7 @@ import org.apache.geode.internal.cache.RegionEntry;
 import org.apache.geode.internal.cache.entries.OffHeapRegionEntry;
 import org.apache.geode.internal.cache.tier.sockets.command.KeySet;
 import org.apache.geode.internal.cache.wan.GatewaySenderEventImpl;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.offheap.OffHeapRegionEntryHelper;
 import org.apache.geode.internal.size.SingleObjectSizer;
 import org.apache.geode.internal.util.ArrayUtils;
@@ -1832,8 +1833,7 @@ public class CustomEntryConcurrentHashMap<K, V> extends AbstractMap<K, V>
         }
         if (!submitted) {
           String name = this.getClass().getSimpleName() + "@" + this.hashCode() + " Clear Thread";
-          Thread thread = new Thread(runnable, name);
-          thread.setDaemon(true);
+          Thread thread = new LoggingThread(name, runnable);
           thread.start();
         }
       }
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/FederatingManager.java b/geode-core/src/main/java/org/apache/geode/management/internal/FederatingManager.java
index 81748ca..6929e46 100755
--- a/geode-core/src/main/java/org/apache/geode/management/internal/FederatingManager.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/FederatingManager.java
@@ -22,7 +22,6 @@ import java.util.concurrent.Callable;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.atomic.AtomicReference;
@@ -50,6 +49,7 @@ import org.apache.geode.internal.cache.HasCachePerfStats;
 import org.apache.geode.internal.cache.InternalCache;
 import org.apache.geode.internal.cache.InternalRegionArguments;
 import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.management.ManagementException;
 
 /**
@@ -116,7 +116,8 @@ public class FederatingManager extends Manager {
       }
 
       Runtime rt = Runtime.getRuntime();
-      this.pooledMembershipExecutor = Executors.newFixedThreadPool(rt.availableProcessors());
+      this.pooledMembershipExecutor =
+          LoggingExecutors.newFixedThreadPool("FederatingManager", false, rt.availableProcessors());
 
       running = true;
       startManagingActivity();
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/LocalManager.java b/geode-core/src/main/java/org/apache/geode/management/internal/LocalManager.java
index 8028dd7..7cfa97f 100644
--- a/geode-core/src/main/java/org/apache/geode/management/internal/LocalManager.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/LocalManager.java
@@ -20,9 +20,7 @@ import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 
 import javax.management.MalformedObjectNameException;
@@ -46,7 +44,7 @@ import org.apache.geode.internal.cache.HasCachePerfStats;
 import org.apache.geode.internal.cache.InternalCache;
 import org.apache.geode.internal.cache.InternalRegionArguments;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.management.ManagementException;
 
 /**
@@ -103,25 +101,8 @@ public class LocalManager extends Manager {
       if (repo.getLocalMonitoringRegion() != null) {
         return;
       } else {
-        ThreadFactory tf = new ThreadFactory() {
-          public Thread newThread(final Runnable command) {
-
-            final Runnable r = new Runnable() {
-              public void run() {
-
-                command.run();
-
-              }
-            };
-            final ThreadGroup group = LoggingThreadGroup.createThreadGroup(
-                ManagementStrings.MANAGEMENT_TASK_THREAD_GROUP.toLocalizedString(), logger);
-            Thread thread =
-                new Thread(group, r, ManagementStrings.MANAGEMENT_TASK.toLocalizedString());
-            thread.setDaemon(true);
-            return thread;
-          }
-        };
-        singleThreadFederationScheduler = Executors.newSingleThreadScheduledExecutor(tf);
+        singleThreadFederationScheduler =
+            LoggingExecutors.newSingleThreadScheduledExecutor("Management Task");
 
         if (logger.isDebugEnabled()) {
           logger.debug("Creating  Management Region :");
@@ -302,7 +283,7 @@ public class LocalManager extends Manager {
 
   /**
    * This task is responsible for pushing data to the hidden region. It is executed in a single
-   * thread from Executors.newSingleThreadScheduledExecutor(); Only one thread will be responsible
+   * thread from newSingleThreadScheduledExecutor; Only one thread will be responsible
    * for pushing the data to the hidden region.
    *
    * (Note however that if this single thread terminates due to a failure during execution prior to
@@ -380,13 +361,7 @@ public class LocalManager extends Manager {
         throw e;
       } catch (Throwable th) {
         SystemFailure.checkFailure();
-        // Catching all run time exception and Errors to
-        // pass to Thread group logger
-        // why is this invoking uncaughtException directly??
-        Thread currentThread = Thread.currentThread();
-        ThreadGroup tg = currentThread.getThreadGroup();
-        tg.uncaughtException(currentThread, th);
-        return;
+        throw th;
       }
       if (logger.isTraceEnabled()) {
         logger.trace("Federation completed at managed node : ");
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/beans/MemberMBeanBridge.java b/geode-core/src/main/java/org/apache/geode/management/internal/beans/MemberMBeanBridge.java
index c137a89..ab3506a 100644
--- a/geode-core/src/main/java/org/apache/geode/management/internal/beans/MemberMBeanBridge.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/beans/MemberMBeanBridge.java
@@ -75,6 +75,7 @@ import org.apache.geode.internal.cache.control.ResourceManagerStats;
 import org.apache.geode.internal.cache.execute.FunctionServiceStats;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.internal.logging.log4j.LogMarker;
 import org.apache.geode.internal.logging.log4j.LogWriterAppender;
@@ -962,20 +963,17 @@ public class MemberMBeanBridge {
   public void shutDownMember() {
     final InternalDistributedSystem ids = dm.getSystem();
     if (ids.isConnected()) {
-      Thread t = new Thread(new Runnable() {
-        public void run() {
-          try {
-            // Allow the Function call to exit
-            Thread.sleep(1000);
-          } catch (InterruptedException ignore) {
-          }
-          ConnectionTable.threadWantsSharedResources();
-          if (ids.isConnected()) {
-            ids.disconnect();
-          }
+      Thread t = new LoggingThread("Shutdown member", false, () -> {
+        try {
+          // Allow the Function call to exit
+          Thread.sleep(1000);
+        } catch (InterruptedException ignore) {
+        }
+        ConnectionTable.threadWantsSharedResources();
+        if (ids.isConnected()) {
+          ids.disconnect();
         }
       });
-      t.setDaemon(false);
       t.start();
     }
   }
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/RebalanceCommand.java b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/RebalanceCommand.java
index 4746394..28de976 100644
--- a/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/RebalanceCommand.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/RebalanceCommand.java
@@ -24,7 +24,6 @@ import java.util.List;
 import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -45,6 +44,7 @@ import org.apache.geode.cache.execute.Function;
 import org.apache.geode.cache.partition.PartitionRebalanceInfo;
 import org.apache.geode.distributed.DistributedMember;
 import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.management.DistributedRegionMXBean;
 import org.apache.geode.management.ManagementService;
 import org.apache.geode.management.cli.CliMetaData;
@@ -77,7 +77,8 @@ public class RebalanceCommand extends InternalGfshCommand {
           help = CliStrings.REBALANCE__SIMULATE__HELP) boolean simulate)
       throws Exception {
 
-    ExecutorService commandExecutors = Executors.newSingleThreadExecutor();
+    ExecutorService commandExecutors =
+        LoggingExecutors.newSingleThreadExecutor("RebalanceCommand", false);
     List<Future<Result>> commandResult = new ArrayList<>();
     Result result;
     try {
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/ShutdownCommand.java b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/ShutdownCommand.java
index 121d615..75c4cbd 100644
--- a/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/ShutdownCommand.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/ShutdownCommand.java
@@ -20,7 +20,6 @@ import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -36,6 +35,7 @@ import org.apache.geode.cache.execute.FunctionService;
 import org.apache.geode.distributed.DistributedMember;
 import org.apache.geode.internal.cache.InternalCache;
 import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.management.cli.CliMetaData;
 import org.apache.geode.management.cli.Result;
 import org.apache.geode.management.internal.cli.AbstractCliAroundInterceptor;
@@ -153,7 +153,7 @@ public class ShutdownCommand extends InternalGfshCommand {
 
   private void shutdownNode(final long timeout, final Set<DistributedMember> includeMembers)
       throws TimeoutException, InterruptedException, ExecutionException {
-    ExecutorService exec = Executors.newSingleThreadExecutor();
+    ExecutorService exec = LoggingExecutors.newSingleThreadExecutor("ShutdownCommand", false);
     try {
       final Function shutDownFunction = new ShutDownFunction();
       logger.info("Gfsh executing shutdown on members " + includeMembers);
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/StartGatewaySenderCommand.java b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/StartGatewaySenderCommand.java
index 7dd482f..a3c466f 100644
--- a/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/StartGatewaySenderCommand.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/StartGatewaySenderCommand.java
@@ -22,10 +22,7 @@ import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.management.ObjectName;
 
@@ -34,6 +31,7 @@ import org.springframework.shell.core.annotation.CliOption;
 
 import org.apache.geode.cache.Cache;
 import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.management.GatewaySenderMXBean;
 import org.apache.geode.management.cli.CliMetaData;
 import org.apache.geode.management.cli.ConverterHint;
@@ -77,15 +75,8 @@ public class StartGatewaySenderCommand extends InternalGfshCommand {
       return ResultBuilder.createUserErrorResult(CliStrings.NO_MEMBERS_FOUND_MESSAGE);
     }
 
-    ExecutorService execService = Executors.newCachedThreadPool(new ThreadFactory() {
-      AtomicInteger threadNum = new AtomicInteger();
-
-      public Thread newThread(final Runnable r) {
-        Thread result = new Thread(r, "Start Sender Command Thread " + threadNum.incrementAndGet());
-        result.setDaemon(true);
-        return result;
-      }
-    });
+    ExecutorService execService =
+        LoggingExecutors.newCachedThreadPool("Start Sender Command Thread ", true);
 
     List<Callable<List>> callables = new ArrayList<>();
 
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/ShutDownFunction.java b/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/ShutDownFunction.java
index bd5de57..bfb470e 100644
--- a/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/ShutDownFunction.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/ShutDownFunction.java
@@ -16,7 +16,6 @@ package org.apache.geode.management.internal.cli.functions;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 
 import org.apache.logging.log4j.Logger;
@@ -25,6 +24,7 @@ import org.apache.geode.cache.execute.FunctionContext;
 import org.apache.geode.distributed.internal.InternalDistributedSystem;
 import org.apache.geode.internal.cache.execute.InternalFunction;
 import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.logging.LoggingExecutors;
 import org.apache.geode.internal.tcp.ConnectionTable;
 
 /**
@@ -65,7 +65,7 @@ public class ShutDownFunction implements InternalFunction {
    */
   private void disconnectInNonDaemonThread(final InternalDistributedSystem ids)
       throws InterruptedException, ExecutionException {
-    ExecutorService exec = Executors.newSingleThreadExecutor();
+    ExecutorService exec = LoggingExecutors.newSingleThreadExecutor("Shutdown Disconnector", false);
     Future future = exec.submit(() -> {
       try {
         // Allow the function call to exit so we don't get disconnect exceptions in the client
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/cli/shell/Gfsh.java b/geode-core/src/main/java/org/apache/geode/management/internal/cli/shell/Gfsh.java
index ca9aeb9..1adaafd 100755
--- a/geode-core/src/main/java/org/apache/geode/management/internal/cli/shell/Gfsh.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/cli/shell/Gfsh.java
@@ -46,6 +46,7 @@ import org.springframework.shell.event.ShellStatus.Status;
 import org.apache.geode.internal.Banner;
 import org.apache.geode.internal.GemFireVersion;
 import org.apache.geode.internal.lang.ClassUtils;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.process.signal.AbstractSignalNotificationHandler;
 import org.apache.geode.internal.util.ArgumentRedactor;
 import org.apache.geode.internal.util.HostName;
@@ -484,7 +485,7 @@ public class Gfsh extends JLineShell {
    * Starts this GemFire Shell with console.
    */
   public void start() {
-    runner = new Thread(this, getShellName());
+    runner = new LoggingThread(getShellName(), false, this);
     runner.start();
   }
 
diff --git a/geode-core/src/test/java/org/apache/geode/cache30/TestHeapLRU.java b/geode-core/src/test/java/org/apache/geode/cache30/TestHeapLRU.java
index a4de6e3..c7f1886 100644
--- a/geode-core/src/test/java/org/apache/geode/cache30/TestHeapLRU.java
+++ b/geode-core/src/test/java/org/apache/geode/cache30/TestHeapLRU.java
@@ -21,7 +21,7 @@ import org.apache.geode.cache.EvictionAction;
 import org.apache.geode.cache.EvictionAttributes;
 import org.apache.geode.cache.Region;
 import org.apache.geode.distributed.DistributedSystem;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 
 /**
  * Tests populating a region with data that is ever-increasing in size. It is used for testing the
@@ -42,29 +42,25 @@ public class TestHeapLRU {
         .create("TestHeapLRU").getName());
     Region region = cache.createRegion("TestDiskRegion", factory.create());
 
-    ThreadGroup tg = LoggingThreadGroup.createThreadGroup("Annoying threads");
-    Thread thread = new Thread(tg, "Annoying thread") {
-      public void run() {
-        try {
-          while (true) {
-            System.out.println("Annoy...");
-            Object[] array = new Object[10 /* * 1024 */];
-            for (int i = 0; i < array.length; i++) {
-              array[i] = new byte[1024];
-              Thread.sleep(10);
-            }
-
-            System.out.println("SYSTEM GC");
-            System.gc();
-            Thread.sleep(1000);
+    Thread thread = new LoggingThread("Annoying thread", () -> {
+      try {
+        while (true) {
+          System.out.println("Annoy...");
+          Object[] array = new Object[10 /* * 1024 */];
+          for (int i = 0; i < array.length; i++) {
+            array[i] = new byte[1024];
+            Thread.sleep(10);
           }
 
-        } catch (InterruptedException ex) {
-          System.err.println("Interrupted"); // FIXME should throw
+          System.out.println("SYSTEM GC");
+          System.gc();
+          Thread.sleep(1000);
         }
+
+      } catch (InterruptedException ex) {
+        System.err.println("Interrupted"); // FIXME should throw
       }
-    };
-    thread.setDaemon(true);
+    });
     // thread.start();
 
     // ArrayList list = new ArrayList();
diff --git a/geode-core/src/test/java/org/apache/geode/internal/cache/tier/sockets/TcpServerFactoryTest.java b/geode-core/src/test/java/org/apache/geode/internal/cache/tier/sockets/TcpServerFactoryTest.java
index ec2bea7..b16655f 100644
--- a/geode-core/src/test/java/org/apache/geode/internal/cache/tier/sockets/TcpServerFactoryTest.java
+++ b/geode-core/src/test/java/org/apache/geode/internal/cache/tier/sockets/TcpServerFactoryTest.java
@@ -28,7 +28,7 @@ public class TcpServerFactoryTest {
   @Test
   public void createsATcpServer() {
     TcpServerFactory factory = new TcpServerFactory();
-    TcpServer server = factory.makeTcpServer(80, null, null, null, null, null, null, null, null);
+    TcpServer server = factory.makeTcpServer(80, null, null, null, null, null, null, null);
     assertTrue(server != null);
   }
 }
diff --git a/geode-core/src/test/java/org/apache/geode/internal/logging/LoggingThreadFactoryTest.java b/geode-core/src/test/java/org/apache/geode/internal/logging/LoggingThreadFactoryTest.java
new file mode 100644
index 0000000..acff02c
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/logging/LoggingThreadFactoryTest.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geode.internal.logging;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+
+import org.junit.Test;
+
+import org.apache.geode.internal.logging.LoggingThreadFactory.CommandWrapper;
+import org.apache.geode.internal.logging.LoggingThreadFactory.ThreadInitializer;
+
+public class LoggingThreadFactoryTest {
+
+  @Test
+  public void verifyFirstThreadName() {
+    LoggingThreadFactory factory = new LoggingThreadFactory("baseName");
+
+    Thread thread = factory.newThread(null);
+
+    assertThat(thread.getName()).isEqualTo("baseName" + 1);
+  }
+
+  @Test
+  public void verifySecondThreadName() {
+    LoggingThreadFactory factory = new LoggingThreadFactory("baseName");
+    factory.newThread(null);
+
+    Thread thread = factory.newThread(null);
+
+    assertThat(thread.getName()).isEqualTo("baseName" + 2);
+  }
+
+  @Test
+  public void verifyThreadsAreDaemons() {
+    LoggingThreadFactory factory = new LoggingThreadFactory("baseName");
+
+    Thread thread = factory.newThread(null);
+
+    assertThat(thread.isDaemon()).isTrue();
+  }
+
+  @Test
+  public void verifyThreadHaveExpectedHandler() {
+    UncaughtExceptionHandler handler = LoggingUncaughtExceptionHandler.getInstance();
+    LoggingThreadFactory factory = new LoggingThreadFactory("baseName");
+
+    Thread thread = factory.newThread(null);
+
+    assertThat(thread.getUncaughtExceptionHandler()).isSameAs(handler);
+  }
+
+  @Test
+  public void verifyThreadInitializerCalledCorrectly() {
+    ThreadInitializer threadInitializer = mock(ThreadInitializer.class);
+    LoggingThreadFactory factory = new LoggingThreadFactory("baseName", threadInitializer, null);
+
+    Thread thread = factory.newThread(null);
+
+    verify(threadInitializer).initialize(thread);
+  }
+
+  @Test
+  public void verifyCommandWrapperNotCalledIfThreadIsNotStarted() {
+    CommandWrapper commandWrapper = mock(CommandWrapper.class);
+    LoggingThreadFactory factory = new LoggingThreadFactory("baseName", commandWrapper);
+
+    Thread thread = factory.newThread(null);
+
+    verify(commandWrapper, never()).invoke(any());
+  }
+
+  @Test
+  public void verifyCommandWrapperCalledIfThreadStarted() throws InterruptedException {
+    CommandWrapper commandWrapper = mock(CommandWrapper.class);
+    Runnable command = mock(Runnable.class);
+    LoggingThreadFactory factory = new LoggingThreadFactory("baseName", commandWrapper);
+
+    Thread thread = factory.newThread(command);
+    thread.start();
+    thread.join();
+
+    verify(commandWrapper).invoke(command);
+  }
+
+  @Test
+  public void verifyCommandCalledIfThreadStarted() throws InterruptedException {
+    Runnable command = mock(Runnable.class);
+    LoggingThreadFactory factory = new LoggingThreadFactory("baseName");
+
+    Thread thread = factory.newThread(command);
+    thread.start();
+    thread.join();
+
+    verify(command).run();
+  }
+}
diff --git a/geode-core/src/test/java/org/apache/geode/internal/logging/LoggingThreadTest.java b/geode-core/src/test/java/org/apache/geode/internal/logging/LoggingThreadTest.java
new file mode 100644
index 0000000..97f5dab
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/logging/LoggingThreadTest.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geode.internal.logging;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+
+import org.junit.Test;
+
+public class LoggingThreadTest {
+
+  @Test
+  public void verifyCreateSetsNameOnThread() {
+    Thread thread = new LoggingThread("loggingThreadName", false, null);
+
+    assertThat(thread.getName()).isEqualTo("loggingThreadName");
+  }
+
+  @Test
+  public void verifyCreateReturnsNonDaemon() {
+    Thread thread = new LoggingThread("loggingThreadName", false, null);
+
+    assertThat(thread.isDaemon()).isFalse();
+  }
+
+  @Test
+  public void verifyCreateSetExpectedHandler() {
+    UncaughtExceptionHandler handler = LoggingUncaughtExceptionHandler.getInstance();
+
+    Thread thread = new LoggingThread("loggingThreadName", false, null);
+
+    assertThat(thread.getUncaughtExceptionHandler()).isSameAs(handler);
+  }
+
+  @Test
+  public void verifyCreateDaemonSetsNameOnThread() {
+    Thread thread = new LoggingThread("loggingThreadName", null);
+
+    assertThat(thread.getName()).isEqualTo("loggingThreadName");
+  }
+
+  @Test
+  public void verifyCreateDaemonReturnsDaemon() {
+    Thread thread = new LoggingThread("loggingThreadName", null);
+
+    assertThat(thread.isDaemon()).isTrue();
+  }
+
+  @Test
+  public void verifyCreateDaemonSetExpectedHandler() {
+    UncaughtExceptionHandler handler = LoggingUncaughtExceptionHandler.getInstance();
+
+    Thread thread = new LoggingThread("loggingThreadName", null);
+
+    assertThat(thread.getUncaughtExceptionHandler()).isSameAs(handler);
+  }
+
+}
diff --git a/geode-core/src/test/java/org/apache/geode/internal/logging/LoggingUncaughtExceptionHandlerTest.java b/geode-core/src/test/java/org/apache/geode/internal/logging/LoggingUncaughtExceptionHandlerTest.java
new file mode 100644
index 0000000..add5125
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/logging/LoggingUncaughtExceptionHandlerTest.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geode.internal.logging;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+
+import org.apache.logging.log4j.Logger;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.distributed.internal.InternalDistributedSystem;
+import org.apache.geode.internal.logging.LoggingUncaughtExceptionHandler.FailureSettor;
+import org.apache.geode.internal.logging.LoggingUncaughtExceptionHandler.Implementation;
+import org.apache.geode.test.junit.categories.LoggingTest;
+
+@Category(LoggingTest.class)
+public class LoggingUncaughtExceptionHandlerTest {
+
+  @Test
+  public void verifyGetInstanceIsNotNull() {
+    UncaughtExceptionHandler handler = LoggingUncaughtExceptionHandler.getInstance();
+
+    assertThat(handler).isNotNull();
+  }
+
+  @Test
+  public void verifyThatSetOnThreadSetsTheThreadsHandler() {
+    Thread thread = new Thread();
+    Implementation handler = new Implementation(null, null);
+
+    handler.setOnThread(thread);
+
+    assertThat(thread.getUncaughtExceptionHandler()).isSameAs(handler);
+  }
+
+  @Test
+  public void verifyThatCallingUncaughtExceptionIncreasesTheCountByOne() {
+    Logger logger = mock(Logger.class);
+    Implementation handler = new Implementation(logger, null);
+    int count = handler.getUncaughtExceptionsCount();
+
+    handler.uncaughtException(null, null);
+
+    assertThat(handler.getUncaughtExceptionsCount()).isEqualTo(count + 1);
+  }
+
+  @Test
+  public void verifyThatCallingClearSetsTheCountToZero() {
+    Logger logger = mock(Logger.class);
+    Implementation handler = new Implementation(logger, null);
+    // force the count to be non-zero
+    handler.uncaughtException(null, null);
+
+    handler.clearUncaughtExceptionsCount();
+
+    assertThat(handler.getUncaughtExceptionsCount()).isEqualTo(0);
+  }
+
+  @Test
+  public void verifyFatalMessageLoggedWhenUncaughtExceptionIsCalled() {
+    Logger logger = mock(Logger.class);
+    Thread thread = mock(Thread.class);
+    Throwable throwable = mock(Throwable.class);
+    Implementation handler = new Implementation(logger, null);
+
+    handler.uncaughtException(thread, throwable);
+
+    verify(logger).fatal("Uncaught exception in thread " + thread, throwable);
+  }
+
+  @Test
+  public void verifyInfoMessageLoggedWhenUncaughtExceptionIsCalledByShutdownHookAndWithNoClassDefFoundError() {
+    Logger logger = mock(Logger.class);
+    Thread thread = new Thread();
+    thread.setName(InternalDistributedSystem.SHUTDOWN_HOOK_NAME);
+    Throwable throwable = mock(NoClassDefFoundError.class);
+    Implementation handler = new Implementation(logger, null);
+
+    handler.uncaughtException(thread, throwable);
+
+    verify(logger).info(
+        "Uncaught exception in thread {0} this message can be disregarded if it occurred during an Application Server shutdown. The Exception message was: {1}",
+        thread, throwable);
+  }
+
+  @Test
+  public void verifySetFailureCalledWhenUncaughtExceptionCalledWithVirtualMachineError() {
+    Logger logger = mock(Logger.class);
+    Thread thread = mock(Thread.class);
+    VirtualMachineError error = mock(VirtualMachineError.class);
+    FailureSettor failureSettor = mock(FailureSettor.class);
+    Implementation handler = new Implementation(logger, failureSettor);
+
+    handler.uncaughtException(thread, error);
+
+    verify(failureSettor).setFailure(error);
+  }
+
+}
diff --git a/geode-cq/src/main/java/org/apache/geode/cache/query/internal/cq/ClientCQImpl.java b/geode-cq/src/main/java/org/apache/geode/cache/query/internal/cq/ClientCQImpl.java
index 86d0423..fad4260 100644
--- a/geode-cq/src/main/java/org/apache/geode/cache/query/internal/cq/ClientCQImpl.java
+++ b/geode-cq/src/main/java/org/apache/geode/cache/query/internal/cq/ClientCQImpl.java
@@ -38,7 +38,7 @@ import org.apache.geode.internal.cache.InternalCache;
 import org.apache.geode.internal.cache.LocalRegion;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingThread;
 import org.apache.geode.internal.logging.log4j.LocalizedMessage;
 import org.apache.geode.security.GemFireSecurityException;
 
@@ -315,36 +315,29 @@ public class ClientCQImpl extends CqQueryImpl implements ClientCQ {
       try {
         if (!this.queuedEvents.isEmpty()) {
           try {
-            Runnable r = new Runnable() {
-              @Override
-              public void run() {
-                Object[] eventArray = null;
-                if (CqQueryImpl.testHook != null) {
-                  testHook.setEventCount(queuedEvents.size());
-                }
-                // Synchronization for the executer thread.
-                synchronized (queuedEventsSynchObject) {
-                  try {
-                    eventArray = queuedEvents.toArray();
-
-                    // Process through the events
-                    for (Object cqEvent : eventArray) {
-                      cqService.invokeListeners(cqName, ClientCQImpl.this, (CqEventImpl) cqEvent);
-                      stats.decQueuedCqListenerEvents();
-                    }
-                  } finally {
-                    // Make sure that we notify waiting threads or else possible dead lock
-                    queuedEvents.clear();
-                    queuedEvents = null;
-                    queuedEventsSynchObject.notify();
+            Thread thread = new LoggingThread("CQEventHandler For " + cqName, () -> {
+              Object[] eventArray = null;
+              if (CqQueryImpl.testHook != null) {
+                testHook.setEventCount(queuedEvents.size());
+              }
+              // Synchronization for the executer thread.
+              synchronized (queuedEventsSynchObject) {
+                try {
+                  eventArray = queuedEvents.toArray();
+
+                  // Process through the events
+                  for (Object cqEvent : eventArray) {
+                    cqService.invokeListeners(cqName, ClientCQImpl.this, (CqEventImpl) cqEvent);
+                    stats.decQueuedCqListenerEvents();
                   }
+                } finally {
+                  // Make sure that we notify waiting threads or else possible dead lock
+                  queuedEvents.clear();
+                  queuedEvents = null;
+                  queuedEventsSynchObject.notify();
                 }
               }
-            };
-            final LoggingThreadGroup group =
-                LoggingThreadGroup.createThreadGroup("CQEventHandler", logger);
-            Thread thread = new Thread(group, r, "CQEventHandler For " + cqName);
-            thread.setDaemon(true);
+            });
             thread.start();
           } catch (Exception ex) {
             if (logger.isDebugEnabled()) {
diff --git a/geode-wan/src/main/java/org/apache/geode/cache/client/internal/locator/wan/WanLocatorDiscovererImpl.java b/geode-wan/src/main/java/org/apache/geode/cache/client/internal/locator/wan/WanLocatorDiscovererImpl.java
index dab6e8c..07e36c8 100644
--- a/geode-wan/src/main/java/org/apache/geode/cache/client/internal/locator/wan/WanLocatorDiscovererImpl.java
+++ b/geode-wan/src/main/java/org/apache/geode/cache/client/internal/locator/wan/WanLocatorDiscovererImpl.java
@@ -16,8 +16,6 @@ package org.apache.geode.cache.client.internal.locator.wan;
 
 import java.util.StringTokenizer;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
 
 import org.apache.logging.log4j.Logger;
 
@@ -26,7 +24,7 @@ import org.apache.geode.distributed.internal.DistributionConfigImpl;
 import org.apache.geode.distributed.internal.WanLocatorDiscoverer;
 import org.apache.geode.internal.admin.remote.DistributionLocatorId;
 import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.LoggingExecutors;
 
 public class WanLocatorDiscovererImpl implements WanLocatorDiscoverer {
 
@@ -43,18 +41,7 @@ public class WanLocatorDiscovererImpl implements WanLocatorDiscoverer {
   @Override
   public void discover(int port, DistributionConfigImpl config,
       LocatorMembershipListener locatorListener, final String hostnameForClients) {
-    final LoggingThreadGroup loggingThreadGroup =
-        LoggingThreadGroup.createThreadGroup("WAN Locator Discovery Logger Group", logger);
-
-    final ThreadFactory threadFactory = new ThreadFactory() {
-      public Thread newThread(final Runnable task) {
-        final Thread thread = new Thread(loggingThreadGroup, task, "WAN Locator Discovery Thread");
-        thread.setDaemon(true);
-        return thread;
-      }
-    };
-
-    this._executor = Executors.newCachedThreadPool(threadFactory);
+    this._executor = LoggingExecutors.newCachedThreadPool("WAN Locator Discovery Thread", true);
     exchangeLocalLocators(port, config, locatorListener, hostnameForClients);
     exchangeRemoteLocators(port, config, locatorListener, hostnameForClients);
     this._executor.shutdown();