public static @NotNull List<ThreadState> parse(String threadDump) {
    List<ThreadState> result = new ArrayList<>();
    StringBuilder lastThreadStack = new StringBuilder();
    ThreadState lastThreadState = null;
    boolean expectingThreadState = false;
    boolean haveNonEmptyStackTrace = false;
    StringBuilder coroutineDump = null;
    for(@NonNls String line: StringUtil.tokenize(threadDump, "\r\n")) {
      if (EventCountDumper.EVENT_COUNTS_HEADER.equals(line)) {
        break;
      }
      if (isCoroutineDumpHeader(line)) {
        coroutineDump = new StringBuilder();
      }
      if (coroutineDump != null) {
        coroutineDump.append(line).append("\n");
        continue;
      }
      if (line.startsWith("============") || line.contains("Java-level deadlock")) {
        break;
      }
      ThreadState state = tryParseThreadStart(line.trim());
      if (state != null) {
        if (lastThreadState != null) {
          lastThreadState.setStackTrace(lastThreadStack.toString(), !haveNonEmptyStackTrace);
        }
        lastThreadState = state;
        result.add(lastThreadState);
        lastThreadStack.setLength(0);
        haveNonEmptyStackTrace = false;
        lastThreadStack.append(line).append("\n");
        expectingThreadState = true;
      }
      else {
        boolean parsedThreadState = false;
        if (expectingThreadState) {
          expectingThreadState = false;
          parsedThreadState = tryParseThreadState(line, lastThreadState);
        }
        lastThreadStack.append(line).append("\n");
        if (!parsedThreadState && line.trim().startsWith("at")) {
          haveNonEmptyStackTrace = true;
        }
      }
    }
    if (lastThreadState != null) {
      lastThreadState.setStackTrace(lastThreadStack.toString(), !haveNonEmptyStackTrace);
    }
    for(ThreadState threadState: result) {
      inferThreadStateDetail(threadState);
    }
    for(ThreadState threadState: result) {
      String lockId = findWaitingForLock(threadState.getStackTrace());
      ThreadState lockOwner = findLockOwner(result, lockId, true);
      if (lockOwner == null) {
        lockOwner = findLockOwner(result, lockId, false);
      }
      if (lockOwner != null) {
        if (threadState.isAwaitedBy(lockOwner)) {
          threadState.addDeadlockedThread(lockOwner);
          lockOwner.addDeadlockedThread(threadState);
        }
        lockOwner.addWaitingThread(threadState);
      }
    }
    sortThreads(result);
    if (coroutineDump != null) {
      ThreadState coroutineState = new ThreadState("Coroutine dump", "undefined");
      coroutineState.setStackTrace(coroutineDump.toString(), false);
      result.add(coroutineState);
    }
    return result;
  }
public static @NotNull List<ThreadState> parse(String threadDump)