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 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.