1:VirtualThread.java
package concurrency;

import java.util.List;
import java.util.Objects;

/**
 * Models virtual-thread state transitions without creating a JVM virtual
 * thread.
 */
public final class VirtualThread {
    public enum State {
        RUNNABLE, RUNNING, WAITING, TERMINATED
    }

    public enum Signal {
        YIELD, BLOCK, DONE
    }

    private final String id;
    private final List<Signal> steps;
    private State state = State.RUNNABLE;
    private int nextStep;

    public VirtualThread(String id, List<Signal> steps) {
        if (id == null || id.isBlank())
            throw new IllegalArgumentException("id must not be blank");
        this.id = id;
        this.steps = List.copyOf(Objects.requireNonNull(steps, "steps"));
        if (this.steps.isEmpty())
            throw new IllegalArgumentException("steps must not be empty");
        if (this.steps.getLast() != Signal.DONE
                || this.steps.subList(0, this.steps.size() - 1).contains(Signal.DONE)) {
            throw new IllegalArgumentException("DONE must be the final step");
        }
    }

    public String id() {
        return id;
    }

    public State state() {
        return state;
    }

    public void mount() {
        requireState(State.RUNNABLE);
        state = State.RUNNING;
    }

    public Signal step() {
        requireState(State.RUNNING);
        if (nextStep >= steps.size())
            throw new IllegalStateException("no step remains");
        return steps.get(nextStep++);
    }

    public void unmount(Signal signal) {
        requireState(State.RUNNING);
        state = switch (Objects.requireNonNull(signal, "signal")) {
            case YIELD -> State.RUNNABLE;
            case BLOCK -> State.WAITING;
            case DONE -> State.TERMINATED;
        };
    }

    public void unblock() {
        requireState(State.WAITING);
        state = State.RUNNABLE;
    }

    private void requireState(State expected) {
        if (state != expected) {
            throw new IllegalStateException(id + " is " + state + ", expected " + expected);
        }
    }
}