Backend API. Server-side code: this runs in a Codename One backend, not in the app on the device.
public final class VirtualThread
- Object
- VirtualThread
A thread of control that is not an OS thread.
One of these per connection is what lets a server keep a context per client without keeping an OS THREAD per client. The difference is not stylistic: a handoff between OS threads measured 21181ns on the machine this was built on, and switching a virtual thread measured 2.6ns.
A virtual thread runs until it finishes or until it asks for bytes that have not arrived, at which point it parks and the host thread goes and runs another one. Parking happens inside the ordinary blocking calls, so the code a virtual thread runs is written in the plain blocking style and does not know it is not a thread – which is the reason to have them rather than callbacks.
Fields
public static final int FINISHED = 0 | resume: the connection is done and the handle should be freed. |
public static final int PARKED_IO = 1 | resume: waiting for bytes; its descriptor goes back to the poller. |
public static final int RUNNABLE = 2 | resume: it gave up its turn but is ready to run again NOW. |
public static final int WAITING = 3 | resume: parked on an OUTBOUND descriptor – a database socket, a TLS peer, an HTTP call – rather than on its own. |
Methods
public static long create(int fd, int stackBytes) | A virtual thread that will serve fd when first resumed. |
public static long createTask(long token, int stackBytes) | A virtual thread that runs a background task: when first resumed it calls Tasks.runVirtual(token). |
public static int resume(long handle) | Run it until it parks, yields or finishes. |
public static int waitCount(long handle) | How many descriptors a virtual thread that answered WAITING waits on; 0 for none, which is a wait on the timeout alone. |
public static int waitDescriptor(long handle, int index) | The index-th descriptor a WAITING virtual thread waits on. |
public static int waitEvents(long handle, int index) | What it waits for on that descriptor: Reactor.READ, Reactor.WRITE or both. |
public static long waitTimeout(long handle) | How long it is willing to wait, in milliseconds from now, or -1 for no bound. |
public static int descriptorOf(long handle) | The descriptor this virtual thread serves, or -1. |
public static void free(long handle) | Release it. |
public static void yieldNow() | Step aside so the host thread can run another virtual thread, without waiting for anything. |
public static long current() | The handle of the virtual thread the caller runs on, or 0 on a host or platform thread. |
public static boolean isVirtual() | Whether the caller is running on a virtual thread that may park – one that holds no monitor. |
public static boolean supported() | Whether this build has virtual threads at all, which is not the same question as isVirtual(). |
public static void report() | Print created/finished/freed counts to stderr, for diagnosis. |
Inherited methods
Field details
FINISHED
public static final int FINISHED = 0resume: the connection is done and the handle should be freed.PARKED_IO
public static final int PARKED_IO = 1resume: waiting for bytes; its descriptor goes back to the poller.RUNNABLE
public static final int RUNNABLE = 2resume: it gave up its turn but is ready to run again NOW.
It is waiting on something that is not its socket – the collector’s allocation backpressure, or its own fairness yield. Putting it on the poller instead would wait for a client that is waiting for the response this virtual thread owes it, and the connection would hang for ever.
WAITING
public static final int WAITING = 3resume: parked on an OUTBOUND descriptor – a database socket, a TLS
peer, an HTTP call – rather than on its own. The host registers the
descriptors waitCount names with its poller and resumes it when one is
ready or waitTimeout runs out, running other virtual threads meanwhile.Method details
create
public static long create(int fd, int stackBytes)A virtual thread that will serve fd when first resumed.
The stack is the C stack only. Java locals and the operand stack live in the virtual thread’s own VM state, which is mapped lazily, so what this size buys is call DEPTH rather than data: it holds the C activation records of the Java methods the connection is nested inside.
Returns
createTask
public static long createTask(long token, int stackBytes)Tasks.runVirtual(token). It has no descriptor –
descriptorOf answers -1 – so its host runs it from the ring and
never parks it on the poller.Returns
resume
public static int resume(long handle)waitCount
public static int waitCount(long handle)WAITING waits on;
0 for none, which is a wait on the timeout alone.waitDescriptor
public static int waitDescriptor(long handle, int index)index-th descriptor a WAITING virtual thread waits on.waitEvents
public static int waitEvents(long handle, int index)Reactor.READ, Reactor.WRITE or both.waitTimeout
public static long waitTimeout(long handle)descriptorOf
public static int descriptorOf(long handle)free
public static void free(long handle)resume has returned FINISHED.yieldNow
public static void yieldNow()Step aside so the host thread can run another virtual thread, without waiting for anything.
Parking happens by itself when bytes have not arrived. This is for the other case: a virtual thread that COULD keep going but has had its turn. A no-op when the caller is not a virtual thread.
current
public static long current()isVirtual
public static boolean isVirtual()synchronized is PINNED to
its host and answers false: it waits the way a platform thread does,
because switching out would let another virtual thread on the same host
into the same critical section, monitor ownership being the host’s.supported
public static boolean supported()report
public static void report()