Java多线程技术二:线程间通信——InheritableThreadLocal的使用
1 概述
使用InheritableThreadLocal可以在子线程中取得父线程继承下来的值。
2 ThreadLocal类不能实现值的继承
public class Tools {
public static ThreadLocal t1 = new ThreadLocal();
}
public class ThreadA extends Thread{
@Override
public void run(){
try {
for (int i = 0; i < 10; i++) {
System.out.println("ThreadA线程中取值 = " + Tools.t1.get());
Thread.sleep(100);
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public class Run1 {
public static void main(String[] args) {
try {
for (int i = 0; i < 10; i++) {
if(Tools.t1.get() == null){
Tools.t1.set("在main方法中,set的值");
}
System.out.println("在main方法中取值 = " + Tools.t1.get());
Thread.sleep(100);
}
Thread.sleep(5000);
ThreadA a = new ThreadA();
a.start();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
因为main线程创建了ThreadA线程,所以main线程时ThreadA的父线程。从运行结果看,由于ThreadA线程并没有继承main线程,所以ThreadLocal并不具有值继承特性,这时就要使用 InheritableThreadLocal类进行替换了。
3 使用InheritableThreadLocal体现值继承特性
使用InheritableThreadLocal类可以让子线程从父线程中继承值。
public class Tools {
public static InheritableThreadLocal t1 = new InheritableThreadLocal();
}
public class ThreadA extends Thread{
@Override
public void run(){
try {
for (int i = 0; i < 10; i++) {
System.out.println("ThreadA线程中取值 = " + Tools.t1.get());
Thread.sleep(100);
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public class Run1 {
public static void main(String[] args) {
try {
for (int i = 0; i < 10; i++) {
if(Tools.t1.get() == null){
Tools.t1.set("在main方法中,set的值");
}
System.out.println("在main方法中取值 = " + Tools.t1.get());
Thread.sleep(100);
}
Thread.sleep(5000);
ThreadA a = new ThreadA();
a.start();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
ThreadA子线程获取的值是从父线程main继承的。
4 值继承特性在源代码中的执行流程
使用InheritableThreadLocal的确可以实现值继承的特性,那么在JDK的源码中是如何实现的呢?下面按步骤来分析。
1、首先看一下InheritableThreadLocal类的源码:
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
在InheritableThreadLocal类的源码中有3个方法,这3个方法都是对父类ThreadLocal中的同名方法进行重写后得到的,因为在源码中并没有使用@override进行标识,所以在初期分析时如果不注意,流程是比较绕的。ThreadLocal类中的三个方法的源码:
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
从上面的源码中可以看出,ThreadLocal类操作的是threadLocals实例变量,而InheritableThreadLocal类操作的是inheritableThreadLocal实例变量,这是2个变量。
2、在main方法中使用main线程执行InheritableThreadLocal.set()方法。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
在执行ThreadLocal.java类中的set()方法时,有两个方法已经被InheritableThreadLocal类重写了,分别是getMap()和createMap()。一定要留意,在执行这两个方法时,调用的是InheritableThreadLocal类中重写的getMap和createMap方法。
3、通过查看InheritableThreadLocal类中的getMap和createMap方法的源码,可以明确一个重要的知识点,那就是不再像Thread类中的ThreadLocal.ThreadLocalMap threadLocals存入数据了,而是在ThreadLocal.ThreadLocalMap inheritableThreadLocals 存入数据,这两个对象在Thread.java类中的声明如下:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
上面的分析步骤明确了一个知识点,就是main线程向inheritableThreadLocals对象写入数据,对象 inheritableThreadLocals就是保存数据的容器,那么子线程如何继承父线程中的inheritableThreadLocals对象的值呢?
4、这个实现的思路就是在创建子线程ThreadA时,子线程主动引用父线程main里面的inheritableThreadLocals对象值,源码如下:
public
class Thread implements Runnable {
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
}
因为init()方法是被Thread的构造方法调用的,所以在new ThreadA()中,在Thread.java源码内部会自动调用init方法。在init()方法中的最后一个参数inheritThreadLocals 代表当前线程对象是否会从父线程中继承值。因为这个值被永远传入true,所以每次都会继承值。
5 父线程有最新的值,子线程还是旧值:不可变类型
public class Tools {
public static InheritableThreadLocal t1 = new InheritableThreadLocal();
}
public class ThreadA extends Thread{
@Override
public void run(){
try {
for (int i = 0; i < 10; i++) {
System.out.println("在ThreadA线程中取值 = " + Tools.t1.get());
Thread.sleep(1000);
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public class Run1 {
public static void main(String[] args) throws InterruptedException {
if(Tools.t1.get() == null){
Tools.t1.set("此值是在main线程存入的");
}
System.out.println("在main线程中取值 = " + Tools.t1.get());
Thread.sleep(1000);
ThreadA a = new ThreadA();
a.start();
Thread.sleep(5000);
Tools.t1.set("此值是在main线程 new 放入的");
}
}
从运行结果可见,子线程还是持有旧的数据。
6 子线程有最新的值,父线程是旧值:不可变类型
public class Tools {
public static InheritableThreadLocal t1 = new InheritableThreadLocal();
}
public class ThreadA extends Thread{
@Override
public void run(){
try {
for (int i = 0; i < 10; i++) {
System.out.println("在ThreadA线程中取值 = " + Tools.t1.get());
Thread.sleep(1000);
if(i == 5){
Tools.t1.set("此值是在ThreadA线程中写入的");
System.out.println("ThreadA已经存在最新值---------");
}
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public class Run1 {
public static void main(String[] args) throws InterruptedException {
if(Tools.t1.get() == null){
Tools.t1.set("此值是在main线程中放入的");
}
System.out.println("在main线程中取值 = " + Tools.t1.get());
Thread.sleep(100);
ThreadA a = new ThreadA();
a.start();
Thread.sleep(3000);
for (int i = 0; i < 10; i++) {
System.out.println("main线程结束,获取的值 = " + Tools.t1.get());
Thread.sleep(1000);
}
}
}
可见main线程中存的永远是旧的数据。
7 子线程可以感应对象属性值的变化:可变类型
前面都是在主、子线程中使用String数据类型做继承特性的实验,如果子线程从父线程继承可变对象数据类型,那么子线程可以得到最新对象中的属性值。
public class UserInfo {
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
public class Tools {
public static InheritableThreadLocal<UserInfo> t1 = new InheritableThreadLocal<>();
}
public class ThreadA extends Thread{
@Override
public void run(){
try {
for (int i = 0; i < 10; i++) {
UserInfo userInfo = Tools.t1.get();
System.out.println("在线程a中取值 = " + userInfo.getUsername() +
";" + userInfo.hashCode());
Thread.sleep(1000);
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public class Run1 {
public static void main(String[] args) throws InterruptedException {
UserInfo userInfo = new UserInfo();
System.out.println("A userinfo = " + userInfo.hashCode());
userInfo.setUsername("中国");
if(Tools.t1.get() == null){
Tools.t1.set(userInfo);
}
System.out.println("在main方法中取值 = " + Tools.t1.get()
+ ";" + Tools.t1.get().hashCode());
Thread.sleep(100);
ThreadA a = new ThreadA();
a.start();
Thread.sleep(4000);
Tools.t1.get().setUsername("美国");
}
}
程序运行结果就是ThreadA取到userinfo对象的最新属性值。如果在main方法的最后重新放入一个新的UserInfo对象,则ThreadA线程打印的结果永远是中国。这是因为ThreadA永远引用的是中国对应的UserInfo对象,并不是新版美国魅影的UserInfo对象,所以依然符合“父线程有最新的值,子线程还是旧值”