-
CSR
-
Resolution: Approved
-
P3
-
None
-
behavioral
-
low
-
-
Java API
-
SE
Summary
java.util.logging.LogRecord
is updated to support long
thread ids. New accessors to support long thread ids are added, and previous accessors working with int
are deprecated.
Problem
LogRecord handles thread id as an int
. This is a long standing behavior that precludes the addition of the Thread::getId
method which returns a long
.
Prior to Java 5, there was no correlation between the LogRecord::getThreadID and the thread id returned by Thread::getId
. In Java 5, JDK-6278014 fixed that to ensure that if Thread::getId
returned a value less than Integer.MAX_VALUE / 2
, it would be directly used as the LogRecord
threadID, otherwise, a
value would be synthesized, by incrementing a counter. In order to ensure that the same value would always be used for the same thread a ThreadLocal<Integer>
was used. The rationale was that it was very unlikely that a system would use more thread than Integer.MAX_VALUE / 2 - so the it would be unlikely to get a syntesized value. However, this assumption needs to be revisited in the context of virtual threads, where billion of virtual threads might not be so unlikely, and where using ThreadLocal
could become a performance drain.
This CSR proposes a solution to upgrade LogRecord to support long
thread ids and get rid of the ThreadLocal.
Solution
LogRecord is updated with the following changes:
- The serial field
threadID
is deprecated, but is kept for backward compatibility. getThreadID
andsetThreadID
methods are deprecated in favor of newgetLongThreadID
andsetLongThreadID
accessors.- A new serial field
longThreadID
is introduced to carry the long thread id as returned byThread::getId
. - long thread ids that are less than
Integer.MAX_VALUE
are directly mapped tothreadID
(andlongThreadID
). This extends the range where the deprecatedthreadID
has the same value than the actual long thread id returned byThread::getId
. - For long thread id greater than
Integer.MAX_VALUE
, a new negative value for the intthreadID
is synthesized, using a deterministic algorithm based on thelongThreadID
hash, and such that the resulting value is guaranteed to be a negative value. - However, the new
longThreadID
field always carries the original long thread id - and new accessors are provided to get/set this field.
In addition - and for maintaining backward compatibility, the following invariant are preserved:
- setting the thread id as an int (using the deprecated setters) sets both fields to the same int value.
- setting the thread id as a long (using the new setter) sets the new longThreadID field to the provided value, and the old
threadID
to a synthesized value as described above.
The exact algorithm used to synthesize a value for the int
value is not documented (it wasn't documented before either) but the documentation, and deprecation of the old accessors, will incite users of the API to use the new longThreadID
.
Specification
src/java.logging/share/classes/java/util/logging/LogRecord.java
/**
* @serialField level Level Logging message level
* @serialField sequenceNumber long Sequence number
* @serialField sourceClassName String Class that issued logging call
* @serialField sourceMethodName String Method that issued logging call
* @serialField message String Non-localized raw message text
- * @serialField threadID int Thread ID for thread that issued logging call
+ * @serialField threadID int this is deprecated and is available for backward compatibility.
+ * Values may have been synthesized. If present, {@code longThreadID} represents
+ * the actual thread id
+ * @serialField longThreadID long Thread ID for thread that issued logging call
* @serialField millis long Truncated event time in milliseconds since 1970
* - calculated as getInstant().toEpochMilli().
* The event time instant can be reconstructed using
* <code>Instant.ofEpochSecond(millis/1000, (millis % 1000) * 1000_000 + nanoAdjustment)</code>
* @serialField nanoAdjustment int Nanoseconds adjustment to the millisecond of
* event time - calculated as getInstant().getNano() % 1000_000
* The event time instant can be reconstructed using
* <code>Instant.ofEpochSecond(millis/1000, (millis % 1000) * 1000_000 + nanoAdjustment)</code>
* <p>
* Since: 9
* @serialField thrown Throwable The Throwable (if any) associated with log
* message
* @serialField loggerName String Name of the source Logger
* @serialField resourceBundleName String Resource bundle name to localized
* log message
*/
...
/**
+ * Initializes the LogRecord from deserialized data.
+ * <ul>
+ * <li>If {@code longThreadID} is present in the serial form, its value
+ * takes precedence over {@code threadID} and a value for {@code threadID}
+ * is synthesized from it, such that for {@code longThreadID} values between
+ * {@code 0} and {@code Integer.MAX_VALUE} inclusive, {@code longThreadID}
+ * and {@code threadID} will have the same value. For values outside of this
+ * range a negative synthesized value will be deterministically derived
+ * from {@code longThreadID}.
+ * <li>Otherwise, when only {@code threadID} is
+ * present, {@code longThreadID} is initialized with the value of
+ * {@code threadID} which may be anything between {@code Integer.MIN_VALUE}
+ * and {Integer.MAX_VALUE}.
+ * </ul>
+ */
+ @Serial
private void readObject(ObjectInputStream in)
...
/**
* Get an identifier for the thread where the message originated.
* <p>
* This is a thread identifier within the Java VM and may or
* may not map to any operating system ID.
*
+ * @deprecated Values returned by this method may be synthesized,
+ * and may not correspond to the actual {@linkplain Thread#getId() thread id}.
+ * Use {@link #getLongThreadID()} instead
* @return thread ID
*/
+ @Deprecated(since = "16")
public int getThreadID() {
...
/**
* Set an identifier for the thread where the message originated.
* @param threadID the thread ID
+ *
+ * @deprecated This method doesn't allow to pass a long {@linkplain Thread#getId() thread id}.
+ * Use {@link #setLongThreadID(long)} setLongThreadID(long longThreadID)} instead
*/
+ @Deprecated(since = "16")
public void setThreadID(int threadID) {
...
**
+ * Get a thread identifier for the thread where message originated
+ *
+ * <p>
+ * This is a thread identifier within the Java VM and may or
+ * may not map to any operating system ID.
+ *
+ * @return thread ID
+ * @since 16
+ */
+ public long getLongThreadID()
...
/**
+ * Set an identifier for the thread where the message originated.
+ *
+ * @param longThreadID the thread ID
+ * @return this log record
+ * @since 16
+ */
+ public LogRecord setLongThreadID(long longThreadID)
- csr of
-
JDK-8245302 Upgrade LogRecord to support long thread ids and remove its usage of ThreadLocal
-
- Resolved
-