Package opennlp.tools.util

# Class LastResultOwnerOrThreadLocal<T>

java.lang.Object
opennlp.tools.util.LastResultOwnerOrThreadLocal<T>

Type Parameters:
  `T` - the type of the stored value (often a decode result such as `Sequence`)

public final class LastResultOwnerOrThreadLocal<T> extends Object
Stores a per-thread "last result" value for APIs that expose probabilities or other data from the most recent decode call (e.g. `tag()` then `probs()`).

Why not always `ThreadLocal`? Short-lived instances that are only ever used from one thread would pay for a `ThreadLocal` map entry on every `set(Object)`. This type keeps the first thread's value in plain fields until a second thread touches the same instance; then it switches non-owner threads to `ThreadLocal` storage. Once the instance has gone multi-threaded, it stays that way for the rest of its lifetime (one-way transition).

Thread identity is tracked by `thread id` (a `long`), not by `Thread` reference. Holding a strong reference to a worker thread in a long-lived component pins the thread's context classloader in container environments (e.g. Jakarta EE) — exactly the leak this class is designed to avoid. Thread ids can be recycled after a thread terminates, so the worst-case outcome is that a recycled-id thread sees a stale `ownerValue` from a previous thread instead of `null`; that is no worse than the documented contract for `get()` (callers are expected to call `set(Object)` before `get()` on every thread).

Call `clearForCurrentThread()` when releasing pooled threads or disposing the enclosing component to avoid classloader retention.

## Relationship to other thread-safety patterns in this PR

OpenNLP's ME classes use three different strategies depending on what they need to share between `decode()` and `probs()` on the same thread:

- `LastResultOwnerOrThreadLocal` / `OwnerOrPerThreadState` — used where the public API exposes per-thread last-result state (`POSTaggerME`, `SentenceDetectorME`, `TokenizerME`). The owner-fast-path keeps single-threaded callers cheap.
- `volatile` field plus method-local processing plus an atomic publish at the end — used where `decode()` returns the result directly and there is no separate `probs()`-style accessor (`ChunkerME`, `LemmatizerME`, `NameFinderME`).
- Plain `ThreadLocal` — used inside the cache layers (`BeamSearch`, `CachedFeatureGenerator`, `ConfigurablePOSContextGenerator`) where the per-call cost of a `ThreadLocal.get()` is dwarfed by the work it guards (e.g. `MaxentModel.eval`).

- ## Constructor Summary

  Constructors
  Constructor
  Description
  `LastResultOwnerOrThreadLocal()`

- ## Method Summary

  Modifier and Type
  Method
  Description
  `void`
  `clearForCurrentThread()`
  Clears `ThreadLocal` state for the current thread and, when this thread is the single-thread owner, clears owner fields so another thread can become owner.
  `T`
  `get()`
  Returns the last stored value for the calling thread, or `null` if none.
  `void`
  `set(T value)`
  Records `value` as the last result for the calling thread.

  ### Methods inherited from class java.lang.Object

  `equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait`

- ## Constructor Details

  - ### LastResultOwnerOrThreadLocal

    public LastResultOwnerOrThreadLocal()

- ## Method Details

  - ### set

    public void set(T value)
    Records `value` as the last result for the calling thread.
    Parameters:
      `value` - the value to store (typically non-null after a successful decode)

  - ### get

    public T get()
    Returns the last stored value for the calling thread, or `null` if none.
    Returns:
      last value for this thread, or `null`

  - ### clearForCurrentThread

    public void clearForCurrentThread()
    Clears `ThreadLocal` state for the current thread and, when this thread is the single-thread owner, clears owner fields so another thread can become owner.