8056152: API to create Threads that do not inherit inheritable thread-local initial values
Reviewed-by: alanb, dholmes, mchung, mr, rriggs
--- a/jdk/src/java.base/share/classes/java/lang/InheritableThreadLocal.java Fri Dec 18 14:43:24 2015 +0100
+++ b/jdk/src/java.base/share/classes/java/lang/InheritableThreadLocal.java Fri Dec 18 16:06:24 2015 +0000
@@ -40,6 +40,11 @@
* maintained in the variable (e.g., User ID, Transaction ID) must be
* automatically transmitted to any child threads that are created.
*
+ * <p>Note: During the creation of a new {@link
+ * Thread#Thread(ThreadGroup,Runnable,String,long,boolean) thread}, it is
+ * possible to <i>opt out</i> of receiving initial values for inheritable
+ * thread-local variables.
+ *
* @author Josh Bloch and Doug Lea
* @see ThreadLocal
* @since 1.2
--- a/jdk/src/java.base/share/classes/java/lang/Thread.java Fri Dec 18 14:43:24 2015 +0100
+++ b/jdk/src/java.base/share/classes/java/lang/Thread.java Fri Dec 18 16:06:24 2015 +0000
@@ -344,11 +344,11 @@
/**
* Initializes a Thread with the current AccessControlContext.
- * @see #init(ThreadGroup,Runnable,String,long,AccessControlContext)
+ * @see #init(ThreadGroup,Runnable,String,long,AccessControlContext,boolean)
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
- init(g, target, name, stackSize, null);
+ init(g, target, name, stackSize, null, true);
}
/**
@@ -361,9 +361,12 @@
* zero to indicate that this parameter is to be ignored.
* @param acc the AccessControlContext to inherit, or
* AccessController.getContext() if null
+ * @param inheritThreadLocals if {@code true}, inherit initial values for
+ * inheritable thread-locals from the constructing thread
*/
private void init(ThreadGroup g, Runnable target, String name,
- long stackSize, AccessControlContext acc) {
+ long stackSize, AccessControlContext acc,
+ boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
@@ -414,7 +417,7 @@
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
- if (parent.inheritableThreadLocals != null)
+ if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
@@ -468,7 +471,7 @@
* This is not a public constructor.
*/
Thread(Runnable target, AccessControlContext acc) {
- init(null, target, "Thread-" + nextThreadNum(), 0, acc);
+ init(null, target, "Thread-" + nextThreadNum(), 0, acc, true);
}
/**
@@ -678,6 +681,62 @@
}
/**
+ * Allocates a new {@code Thread} object so that it has {@code target}
+ * as its run object, has the specified {@code name} as its name,
+ * belongs to the thread group referred to by {@code group}, has
+ * the specified {@code stackSize}, and inherits initial values for
+ * {@linkplain InheritableThreadLocal inheritable thread-local} variables
+ * if {@code inheritThreadLocals} is {@code true}.
+ *
+ * <p> This constructor is identical to {@link
+ * #Thread(ThreadGroup,Runnable,String,long)} with the added ability to
+ * suppress, or not, the inheriting of initial values for inheritable
+ * thread-local variables from the constructing thread. This allows for
+ * finer grain control over inheritable thread-locals. Care must be taken
+ * when passing a value of {@code false} for {@code inheritThreadLocals},
+ * as it may lead to unexpected behavior if the new thread executes code
+ * that expects a specific thread-local value to be inherited.
+ *
+ * <p> Specifying a value of {@code true} for the {@code inheritThreadLocals}
+ * parameter will cause this constructor to behave exactly like the
+ * {@code Thread(ThreadGroup, Runnable, String, long)} constructor.
+ *
+ * @param group
+ * the thread group. If {@code null} and there is a security
+ * manager, the group is determined by {@linkplain
+ * SecurityManager#getThreadGroup SecurityManager.getThreadGroup()}.
+ * If there is not a security manager or {@code
+ * SecurityManager.getThreadGroup()} returns {@code null}, the group
+ * is set to the current thread's thread group.
+ *
+ * @param target
+ * the object whose {@code run} method is invoked when this thread
+ * is started. If {@code null}, this thread's run method is invoked.
+ *
+ * @param name
+ * the name of the new thread
+ *
+ * @param stackSize
+ * the desired stack size for the new thread, or zero to indicate
+ * that this parameter is to be ignored
+ *
+ * @param inheritThreadLocals
+ * if {@code true}, inherit initial values for inheritable
+ * thread-locals from the constructing thread, otherwise no initial
+ * values are inherited
+ *
+ * @throws SecurityException
+ * if the current thread cannot create a thread in the specified
+ * thread group
+ *
+ * @since 9
+ */
+ public Thread(ThreadGroup group, Runnable target, String name,
+ long stackSize, boolean inheritThreadLocals) {
+ init(group, target, name, stackSize, null, inheritThreadLocals);
+ }
+
+ /**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the <code>run</code> method of this thread.
* <p>
--- a/jdk/src/java.base/share/classes/java/lang/ref/Finalizer.java Fri Dec 18 14:43:24 2015 +0100
+++ b/jdk/src/java.base/share/classes/java/lang/ref/Finalizer.java Fri Dec 18 16:06:24 2015 +0000
@@ -29,7 +29,6 @@
import java.security.AccessController;
import jdk.internal.misc.JavaLangAccess;
import jdk.internal.misc.SharedSecrets;
-import sun.misc.ManagedLocalsThread;
import sun.misc.VM;
final class Finalizer extends FinalReference<Object> { /* Package-private; must be in
@@ -131,7 +130,7 @@
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
- Thread sft = new ManagedLocalsThread(tg, proc, "Secondary finalizer");
+ Thread sft = new Thread(tg, proc, "Secondary finalizer", 0, false);
sft.start();
try {
sft.join();
@@ -190,10 +189,10 @@
}}});
}
- private static class FinalizerThread extends ManagedLocalsThread {
+ private static class FinalizerThread extends Thread {
private volatile boolean running;
FinalizerThread(ThreadGroup g) {
- super(g, "Finalizer");
+ super(g, null, "Finalizer", 0, false);
}
public void run() {
// in case of recursive call to run()
--- a/jdk/src/java.base/share/classes/java/lang/ref/Reference.java Fri Dec 18 14:43:24 2015 +0100
+++ b/jdk/src/java.base/share/classes/java/lang/ref/Reference.java Fri Dec 18 16:06:24 2015 +0000
@@ -29,7 +29,6 @@
import jdk.internal.HotSpotIntrinsicCandidate;
import jdk.internal.misc.JavaLangRefAccess;
import jdk.internal.misc.SharedSecrets;
-import sun.misc.ManagedLocalsThread;
/**
* Abstract base class for reference objects. This class defines the
@@ -128,7 +127,7 @@
/* High-priority thread to enqueue pending References
*/
- private static class ReferenceHandler extends ManagedLocalsThread {
+ private static class ReferenceHandler extends Thread {
private static void ensureClassInitialized(Class<?> clazz) {
try {
@@ -147,7 +146,7 @@
}
ReferenceHandler(ThreadGroup g, String name) {
- super(g, name);
+ super(g, null, name, 0, false);
}
public void run() {
--- a/jdk/src/java.base/share/classes/sun/misc/GC.java Fri Dec 18 14:43:24 2015 +0100
+++ b/jdk/src/java.base/share/classes/sun/misc/GC.java Fri Dec 18 16:06:24 2015 +0000
@@ -82,7 +82,7 @@
*/
public static native long maxObjectInspectionAge();
- private static class Daemon extends ManagedLocalsThread {
+ private static class Daemon extends Thread {
public void run() {
for (;;) {
@@ -122,7 +122,7 @@
}
private Daemon(ThreadGroup tg) {
- super(tg, "GC Daemon");
+ super(tg, null, "GC Daemon", 0L, false);
}
/* Create a new daemon thread in the root thread group */
--- a/jdk/src/java.base/share/classes/sun/misc/InnocuousThread.java Fri Dec 18 14:43:24 2015 +0100
+++ b/jdk/src/java.base/share/classes/sun/misc/InnocuousThread.java Fri Dec 18 16:06:24 2015 +0000
@@ -35,8 +35,10 @@
* A thread that has no permissions, is not a member of any user-defined
* ThreadGroup and supports the ability to erase ThreadLocals.
*/
-public final class InnocuousThread extends ManagedLocalsThread {
+public final class InnocuousThread extends Thread {
private static final jdk.internal.misc.Unsafe UNSAFE;
+ private static final long THREAD_LOCALS;
+ private static final long INHERITABLE_THREAD_LOCALS;
private static final ThreadGroup INNOCUOUSTHREADGROUP;
private static final AccessControlContext ACC;
private static final long INHERITEDACCESSCONTROLCONTEXT;
@@ -54,7 +56,7 @@
}
public InnocuousThread(ThreadGroup group, Runnable target, String name) {
- super(group, target, name);
+ super(group, target, name, 0L, false);
UNSAFE.putOrderedObject(this, INHERITEDACCESSCONTROLCONTEXT, ACC);
UNSAFE.putOrderedObject(this, CONTEXTCLASSLOADER, ClassLoader.getSystemClassLoader());
}
@@ -73,6 +75,14 @@
throw new SecurityException("setContextClassLoader");
}
+ /**
+ * Drops all thread locals (and inherited thread locals).
+ */
+ public final void eraseThreadLocals() {
+ UNSAFE.putObject(this, THREAD_LOCALS, null);
+ UNSAFE.putObject(this, INHERITABLE_THREAD_LOCALS, null);
+ }
+
// ensure run method is run only once
private volatile boolean hasRun;
@@ -96,6 +106,10 @@
Class<?> tk = Thread.class;
Class<?> gk = ThreadGroup.class;
+ THREAD_LOCALS = UNSAFE.objectFieldOffset
+ (tk.getDeclaredField("threadLocals"));
+ INHERITABLE_THREAD_LOCALS = UNSAFE.objectFieldOffset
+ (tk.getDeclaredField("inheritableThreadLocals"));
INHERITEDACCESSCONTROLCONTEXT = UNSAFE.objectFieldOffset
(tk.getDeclaredField("inheritedAccessControlContext"));
CONTEXTCLASSLOADER = UNSAFE.objectFieldOffset
--- a/jdk/src/java.base/share/classes/sun/misc/Signal.java Fri Dec 18 14:43:24 2015 +0100
+++ b/jdk/src/java.base/share/classes/sun/misc/Signal.java Fri Dec 18 16:06:24 2015 +0000
@@ -213,7 +213,7 @@
}
};
if (handler != null) {
- new ManagedLocalsThread(runnable, sig + " handler").start();
+ new Thread(null, runnable, sig + " handler", 0, false).start();
}
}
--- a/jdk/src/java.base/share/classes/sun/net/NetworkServer.java Fri Dec 18 14:43:24 2015 +0100
+++ b/jdk/src/java.base/share/classes/sun/net/NetworkServer.java Fri Dec 18 16:06:24 2015 +0000
@@ -27,7 +27,6 @@
import java.io.*;
import java.net.Socket;
import java.net.ServerSocket;
-import sun.misc.ManagedLocalsThread;
/**
* This is the base class for network servers. To define a new type
@@ -73,7 +72,7 @@
NetworkServer n = (NetworkServer)clone();
n.serverSocket = null;
n.clientSocket = ns;
- new ManagedLocalsThread(n).start();
+ new Thread(null, n, "NetworkServer", 0, false).start();
} catch(Exception e) {
System.out.print("Server failure\n");
e.printStackTrace();
@@ -108,7 +107,7 @@
for each new connection. */
public final void startServer(int port) throws IOException {
serverSocket = new ServerSocket(port, 50);
- serverInstance = new ManagedLocalsThread(this);
+ serverInstance = new Thread(null, this, "NetworkServer", 0, false);
serverInstance.start();
}
--- a/jdk/src/java.base/share/classes/sun/net/www/MimeLauncher.java Fri Dec 18 14:43:24 2015 +0100
+++ b/jdk/src/java.base/share/classes/sun/net/www/MimeLauncher.java Fri Dec 18 16:06:24 2015 +0000
@@ -27,9 +27,8 @@
import java.net.URL;
import java.io.*;
import java.util.StringTokenizer;
-import sun.misc.ManagedLocalsThread;
-class MimeLauncher extends ManagedLocalsThread {
+class MimeLauncher extends Thread {
java.net.URLConnection uc;
MimeEntry m;
String genericTempFileTemplate;
@@ -38,7 +37,7 @@
MimeLauncher (MimeEntry M, java.net.URLConnection uc,
InputStream is, String tempFileTemplate, String threadName) throws ApplicationLaunchException {
- super(threadName);
+ super(null, null, threadName, 0, false);
m = M;
this.uc = uc;
this.is = is;
--- a/jdk/src/java.base/share/classes/sun/nio/fs/AbstractPoller.java Fri Dec 18 14:43:24 2015 +0100
+++ b/jdk/src/java.base/share/classes/sun/nio/fs/AbstractPoller.java Fri Dec 18 16:06:24 2015 +0000
@@ -30,7 +30,6 @@
import java.security.PrivilegedAction;
import java.io.IOException;
import java.util.*;
-import sun.misc.ManagedLocalsThread;
/**
* Base implementation of background poller thread used in watch service
@@ -60,7 +59,7 @@
AccessController.doPrivileged(new PrivilegedAction<>() {
@Override
public Object run() {
- Thread thr = new ManagedLocalsThread(thisRunnable);
+ Thread thr = new Thread(null, thisRunnable, "FileSystemWatchService", 0, false);
thr.setDaemon(true);
thr.start();
return null;
--- a/jdk/src/java.base/share/classes/sun/nio/fs/Cancellable.java Fri Dec 18 14:43:24 2015 +0100
+++ b/jdk/src/java.base/share/classes/sun/nio/fs/Cancellable.java Fri Dec 18 16:06:24 2015 +0000
@@ -25,7 +25,6 @@
package sun.nio.fs;
-import sun.misc.ManagedLocalsThread;
import jdk.internal.misc.Unsafe;
import java.util.concurrent.ExecutionException;
@@ -118,7 +117,7 @@
* thread by writing into the memory location that it polls cooperatively.
*/
static void runInterruptibly(Cancellable task) throws ExecutionException {
- Thread t = new ManagedLocalsThread(task);
+ Thread t = new Thread(null, task, "NIO-Task", 0, false);
t.start();
boolean cancelledByInterrupt = false;
while (t.isAlive()) {
--- a/jdk/src/java.base/share/classes/sun/nio/fs/PollingWatchService.java Fri Dec 18 14:43:24 2015 +0100
+++ b/jdk/src/java.base/share/classes/sun/nio/fs/PollingWatchService.java Fri Dec 18 16:06:24 2015 +0000
@@ -35,7 +35,6 @@
import java.util.*;
import java.util.concurrent.*;
import com.sun.nio.file.SensitivityWatchEventModifier;
-import sun.misc.ManagedLocalsThread;
/**
* Simple WatchService implementation that uses periodic tasks to poll
@@ -59,7 +58,7 @@
.newSingleThreadScheduledExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
- Thread t = new ManagedLocalsThread(r);
+ Thread t = new Thread(null, r, "FileSystemWatchService", 0, false);
t.setDaemon(true);
return t;
}});
--- a/jdk/src/java.base/share/classes/sun/security/provider/SeedGenerator.java Fri Dec 18 14:43:24 2015 +0100
+++ b/jdk/src/java.base/share/classes/sun/security/provider/SeedGenerator.java Fri Dec 18 16:06:24 2015 +0000
@@ -75,7 +75,6 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Random;
-import sun.misc.ManagedLocalsThread;
import sun.security.util.Debug;
abstract class SeedGenerator {
@@ -305,9 +304,11 @@
}
finalsg[0] = new ThreadGroup
(group, "SeedGenerator ThreadGroup");
- Thread newT = new ManagedLocalsThread(finalsg[0],
+ Thread newT = new Thread(finalsg[0],
ThreadedSeedGenerator.this,
- "SeedGenerator Thread");
+ "SeedGenerator Thread",
+ 0,
+ false);
newT.setPriority(Thread.MIN_PRIORITY);
newT.setDaemon(true);
return newT;
@@ -342,8 +343,8 @@
// Start some noisy threads
try {
BogusThread bt = new BogusThread();
- Thread t = new ManagedLocalsThread
- (seedGroup, bt, "SeedGenerator Thread");
+ Thread t = new Thread
+ (seedGroup, bt, "SeedGenerator Thread", 0, false);
t.start();
} catch (Exception e) {
throw new InternalError("internal error: " +
--- a/jdk/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java Fri Dec 18 14:43:24 2015 +0100
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java Fri Dec 18 16:06:24 2015 +0000
@@ -40,7 +40,6 @@
import javax.crypto.BadPaddingException;
import javax.net.ssl.*;
-import sun.misc.ManagedLocalsThread;
import jdk.internal.misc.JavaNetInetAddressAccess;
import jdk.internal.misc.SharedSecrets;
@@ -1153,10 +1152,13 @@
HandshakeCompletedEvent event =
new HandshakeCompletedEvent(this, sess);
- Thread thread = new ManagedLocalsThread(
+ Thread thread = new Thread(
+ null,
new NotifyHandshake(
handshakeListeners.entrySet(), event),
- "HandshakeCompletedNotify-Thread");
+ "HandshakeCompletedNotify-Thread",
+ 0,
+ false);
thread.start();
}
}
--- a/jdk/src/java.base/windows/classes/sun/nio/ch/WindowsSelectorImpl.java Fri Dec 18 14:43:24 2015 +0100
+++ b/jdk/src/java.base/windows/classes/sun/nio/ch/WindowsSelectorImpl.java Fri Dec 18 16:06:24 2015 +0000
@@ -40,7 +40,6 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
-import sun.misc.ManagedLocalsThread;
/**
* A multi-threaded implementation of Selector for Windows.
@@ -404,13 +403,14 @@
}
// Represents a helper thread used for select.
- private final class SelectThread extends ManagedLocalsThread {
+ private final class SelectThread extends Thread {
private final int index; // index of this thread
final SubSelector subSelector;
private long lastRun = 0; // last run number
private volatile boolean zombie;
// Creates a new thread
private SelectThread(int i) {
+ super(null, null, "SelectorHelper", 0, false);
this.index = i;
this.subSelector = new SubSelector(i);
//make sure we wait for next round of poll
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/Thread/ITLConstructor.java Fri Dec 18 16:06:24 2015 +0000
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary Basic test for Thread(ThreadGroup,Runnable,String,long,boolean)
+ */
+
+public class ITLConstructor {
+ static InheritableThreadLocal<Integer> n = new InheritableThreadLocal<>() {
+ protected Integer initialValue() {
+ return 0;
+ }
+
+ protected Integer childValue(Integer parentValue) {
+ return parentValue + 1;
+ }
+ };
+
+ static final int CHILD_THREAD_COUNT = 10;
+
+ public static void main(String args[]) throws Exception {
+ test(true);
+ test(false);
+ }
+
+ static void test(boolean inherit) throws Exception {
+ // concurrent access to separate indexes is ok
+ int[] x = new int[CHILD_THREAD_COUNT];
+ Thread child = new Thread(Thread.currentThread().getThreadGroup(),
+ new AnotherRunnable(0, x, inherit),
+ "ITLConstructor-thread-"+(0),
+ 0,
+ inherit);
+ child.start();
+ child.join(); // waits for *all* threads to complete
+
+ // Check results
+ for(int i=0; i<CHILD_THREAD_COUNT; i++) {
+ int expectedValue = 1;
+ if (inherit)
+ expectedValue = i+1;
+
+ if (x[i] != expectedValue)
+ throw (new Exception("Got x[" + i + "] = " + x[i]
+ + ", expected: " + expectedValue));
+ }
+ }
+
+ static class AnotherRunnable implements Runnable {
+ final int threadId;
+ final int[] x;
+ final boolean inherit;
+ AnotherRunnable(int threadId, int[] x, boolean inherit) {
+ this.threadId = threadId;
+ this.x = x;
+ this.inherit = inherit;
+ }
+
+ public void run() {
+ int itlValue = n.get();
+
+ if (threadId < CHILD_THREAD_COUNT-1) {
+ Thread child = new Thread(Thread.currentThread().getThreadGroup(),
+ new AnotherRunnable(threadId+1, x, inherit),
+ "ITLConstructor-thread-" + (threadId+1),
+ 0,
+ inherit);
+ child.start();
+ try {
+ child.join();
+ } catch(InterruptedException e) {
+ throw(new RuntimeException("Interrupted", e));
+ }
+ }
+
+ x[threadId] = itlValue+1;
+ }
+ }
+}