-
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
threadIDis deprecated, but is kept for backward compatibility. getThreadIDandsetThreadIDmethods are deprecated in favor of newgetLongThreadIDandsetLongThreadIDaccessors.- A new serial field
longThreadIDis introduced to carry the long thread id as returned byThread::getId. - long thread ids that are less than
Integer.MAX_VALUEare directly mapped tothreadID(andlongThreadID). This extends the range where the deprecatedthreadIDhas 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 intthreadIDis synthesized, using a deterministic algorithm based on thelongThreadIDhash, and such that the resulting value is guaranteed to be a negative value. - However, the new
longThreadIDfield 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
threadIDto 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
-