diff a/src/java.base/share/classes/jdk/internal/misc/CarrierThread.java b/src/java.base/share/classes/jdk/internal/misc/CarrierThread.java
--- a/src/java.base/share/classes/jdk/internal/misc/CarrierThread.java
+++ b/src/java.base/share/classes/jdk/internal/misc/CarrierThread.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this
@@ -30,11 +30,13 @@
 import java.security.PrivilegedAction;
 import java.security.ProtectionDomain;
 import java.util.concurrent.ForkJoinPool;
 import java.util.concurrent.ForkJoinWorkerThread;
 import jdk.internal.access.JavaLangAccess;
+import jdk.internal.access.JavaUtilConcurrentFJPAccess;
 import jdk.internal.access.SharedSecrets;
+import jdk.internal.vm.Continuation;
 
 /**
  * A ForkJoinWorkerThread that can be used as a carrier thread.
  */
 public class CarrierThread extends ForkJoinWorkerThread {
@@ -47,42 +49,66 @@
 
     private static final long CONTEXTCLASSLOADER;
     private static final long INHERITABLETHREADLOCALS;
     private static final long INHERITEDACCESSCONTROLCONTEXT;
 
-    private boolean blocking;    // true if in blocking op
+    // compensating state
+    private static final int NOT_COMPENSATING = 0;
+    private static final int COMPENSATE_IN_PROGRESS = 1;
+    private static final int COMPENSATING = 2;
+    private int compensating;
+
+    // FJP value to adjust release counts
+    private long compensateValue;
 
     @SuppressWarnings("this-escape")
     public CarrierThread(ForkJoinPool pool) {
         super(CARRIER_THREADGROUP, pool, true);
         U.putReference(this, CONTEXTCLASSLOADER, ClassLoader.getSystemClassLoader());
         U.putReference(this, INHERITABLETHREADLOCALS, null);
         U.putReferenceRelease(this, INHERITEDACCESSCONTROLCONTEXT, INNOCUOUS_ACC);
     }
 
     /**
-     * For use by {@link Blocker} to test if the thread is in a blocking operation.
+     * Mark the start of a blocking operation.
      */
-    boolean inBlocking() {
-        //assert JLA.currentCarrierThread() == this;
-        return blocking;
-    }
+    public boolean beginBlocking() {
+        assert Thread.currentThread().isVirtual() && JLA.currentCarrierThread() == this;
+        assert compensating == NOT_COMPENSATING || compensating == COMPENSATING;
 
-    /**
-     * For use by {@link Blocker} to mark the start of a blocking operation.
-     */
-    void beginBlocking() {
-        //assert JLA.currentCarrierThread() == this && !blocking;
-        blocking = true;
+        if (compensating == NOT_COMPENSATING) {
+            // don't preempt when attempting to compensate
+            Continuation.pin();
+            try {
+                compensating = COMPENSATE_IN_PROGRESS;
+
+                // Uses FJP.tryCompensate to start or re-activate a spare thread
+                compensateValue = ForkJoinPools.beginCompensatedBlock(getPool());
+                compensating = COMPENSATING;
+                return true;
+            } catch (Throwable e) {
+                // exception starting spare thread
+                compensating = NOT_COMPENSATING;
+                throw e;
+            } finally {
+                Continuation.unpin();
+            }
+        } else {
+            return false;
+        }
     }
 
     /**
-     * For use by {@link Blocker} to mark the end of a blocking operation.
+     * Mark the end of a blocking operation.
      */
-    void endBlocking() {
-        //assert JLA.currentCarrierThread() == this && blocking;
-        blocking = false;
+    public void endBlocking() {
+        assert Thread.currentThread() == this || JLA.currentCarrierThread() == this;
+        if (compensating == COMPENSATING) {
+            ForkJoinPools.endCompensatedBlock(getPool(), compensateValue);
+            compensating = NOT_COMPENSATING;
+            compensateValue = 0;
+        }
     }
 
     @Override
     public void setUncaughtExceptionHandler(UncaughtExceptionHandler ueh) { }
 
@@ -93,11 +119,11 @@
 
     /**
      * The thread group for the carrier threads.
      */
     @SuppressWarnings("removal")
-    private static final ThreadGroup carrierThreadGroup() {
+    private static ThreadGroup carrierThreadGroup() {
         return AccessController.doPrivileged(new PrivilegedAction<ThreadGroup>() {
             public ThreadGroup run() {
                 ThreadGroup group = JLA.currentCarrierThread().getThreadGroup();
                 for (ThreadGroup p; (p = group.getParent()) != null; )
                     group = p;
@@ -115,10 +141,25 @@
         return new AccessControlContext(new ProtectionDomain[] {
                 new ProtectionDomain(null, null)
         });
     }
 
+    /**
+     * Defines static methods to invoke non-public ForkJoinPool methods via the
+     * shared secret support.
+     */
+    private static class ForkJoinPools {
+        private static final JavaUtilConcurrentFJPAccess FJP_ACCESS =
+                SharedSecrets.getJavaUtilConcurrentFJPAccess();
+        static long beginCompensatedBlock(ForkJoinPool pool) {
+            return FJP_ACCESS.beginCompensatedBlock(pool);
+        }
+        static void endCompensatedBlock(ForkJoinPool pool, long post) {
+            FJP_ACCESS.endCompensatedBlock(pool, post);
+        }
+    }
+
     static {
         CONTEXTCLASSLOADER = U.objectFieldOffset(Thread.class,
                 "contextClassLoader");
         INHERITABLETHREADLOCALS = U.objectFieldOffset(Thread.class,
                 "inheritableThreadLocals");
