This is the code that updates the compiler thread count:
static bool can_remove(CompilerThread *ct, bool do_it) {
  assert(UseDynamicNumberOfCompilerThreads, "or shouldn't be here");
  if (!ReduceNumberOfCompilerThreads) return false;
  AbstractCompiler *compiler = ct->compiler();
  int compiler_count = compiler->num_compiler_threads();
  bool c1 = compiler->is_c1();
  // Keep at least 1 compiler thread of each type.
  if (compiler_count < 2) return false;
  // Keep thread alive for at least some time.
  if (ct->idle_time_millis() < (c1 ? 500 : 100)) return false;
  // We only allow the last compiler thread of each type to get removed.
  jobject last_compiler = c1 ? CompileBroker::compiler1_object(compiler_count - 1)
                             : CompileBroker::compiler2_object(compiler_count - 1);
  if (oopDesc::equals(ct->threadObj(), JNIHandles::resolve_non_null(last_compiler))) {
    if (do_it) {
      assert_locked_or_safepoint(CompileThread_lock); // Update must be consistent.
      compiler->set_num_compiler_threads(compiler_count - 1);
    }
    return true;
  }
  return false;
}
We read the count initially with no lock held:
  int compiler_count = compiler->num_compiler_threads();
we then later update the count with the lock held:
      assert_locked_or_safepoint(CompileThread_lock); // Update must be consistent.
      compiler->set_num_compiler_threads(compiler_count - 1);
but by this time the local variable "compiler_count" may not contain the current number of compiler threads and so we will set the wrong value. Further the count may also have changed when we use it here:
 jobject last_compiler = c1 ? CompileBroker::compiler1_object(compiler_count - 1)
                             : CompileBroker::compiler2_object(compiler_count - 1);
In addition there is logic to try and ensure we always keep at least one compiler thread but this seems to be executed with no locking so multiple threads can execute:
  if (compiler_count < 2) return false;
and all see a count >=2 but then all continue to exit thus leaving zero compiler threads running.