【并发编程】(十一)父子线程数据共享——InheritableThreadLocal原理

文章目录

  • 1.InheritableThreadLocal的作用
    • 1.1.父子线程的定义
  • 2.父线程向子线程传递数据
    • 2.1.InheritableThreadLocal的使用.
    • 2.2.父子线程数据共享的实现原理
      • 2.2.1.InheritableThreadLocal类
      • 2.2.2.如何实现数据共享
      • 2.2.3.childValue()方法
  • 3.线程池中的线程数据传递失效

1.InheritableThreadLocal的作用

在前面的博客《线程安全的代码及ThreadLocal的使用》中说到了ThreadLocal可以在同一个线程中实现参数传递,如果在某些需要异步处理的情况下,就需要在当前线程的执行逻辑中新启动一个子线程,那这个子线程如何共享父线程的参数呢?

除了通过方法形参传入之外,还可以通过InheritableThreadLocal在子线程创建的时候从父线程中同步数据。

1.1.父子线程的定义

在一个线程中执行new Thread()来创建线程,那么当前线程就是这个新创建线程的父线程。我们任意打开一个Thread的构造方法,可以看到里面调用了一个init()方法。

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

进入这个方法中,会找到一行代码Thread parent = currentThread();,在新的线程对象的初始化过程中,就会依赖这个parent父线程中“继承”一部分数据,我们现在要聊的InheritableThreadLocal也是通过这里的parent来实现父子线程数据共享的。

2.父线程向子线程传递数据

2.1.InheritableThreadLocal的使用.

下面使用一个简单Demo来演示父子线程的数据共享:

public class InheritableThreadLocalDemo {

    static InheritableThreadLocal<String> local = new InheritableThreadLocal<>();

    public static void testParent() {
        local.set("I'm parent");
        Thread child = new Thread(InheritableThreadLocalDemo::testChild, "子线程");
        child.start();
        try {
            child.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 查看父线程的值是否对子线程有影响
        print();
    }

    public static void testChild() {
        print();
        // 在子线程中修改值,后续查看父线程是否受影响
        local.set("I'm child");
        // 查看子线程是否修改成功
        print();
    }

    public static void print() {
        System.out.println("当前线程为:" + Thread.currentThread().getName() + "; " +
                "当前ThreadLocal中的值为:" + local.get());
    }

    public static void main(String[] args) {
        // 命名为父线程,方便后面打印结果
        Thread parent = new Thread(InheritableThreadLocalDemo::testParent, "父线程");
        parent.start();
    }

}

当前线程为:子线程; 当前ThreadLocal中的值为:I’m parent
当前线程为:子线程; 当前ThreadLocal中的值为:I’m child
当前线程为:父线程; 当前ThreadLocal中的值为:I’m parent

打印的结果显示,子线程在没有set值的情况下的,可以直接使用到父线程在InheritableThreadLocal设置的值,这里是实现了父线程向子线程共享数据
接着使用子线程重新设置了一个新的值,这个值的作为范围只在子线程上,不会影响到父线程的数据。

2.2.父子线程数据共享的实现原理

2.2.1.InheritableThreadLocal类

InheritableThreadLocalThreadLocal的一个子类,大部分的实现原理是一样的,可以看一下上一篇《线程本地变量的实现——ThreadLocal原理详解》,然后看看有差异的地方,里面重写了里面的三个方法,childValue()后面在聊,另外两个重写的方法,其实就是把对Thread对象中的threadLocals变量的操作替换成了对inheritableThreadLocals变量的操作,就这么一点区别。

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);
    }
}

补充一下:Thread类中与ThreadLocalMap相关的两个变量。
【并发编程】(十一)父子线程数据共享——InheritableThreadLocal原理_第1张图片

2.2.2.如何实现数据共享

既然InheritableThreadLocalThreadLocal并没有多大区别,那它是怎么实现的父线程向子线程传递数据呢?
在上面1.1中说到了一个parent变量,在Threadinit()方法中有这么几行代码:

 if (parent.inheritableThreadLocals != null) {
 	this.inheritableThreadLocals =
 		ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
 }

上面的this指的是调用new Thread()方法时新增的Thread对象。后面调用createInheritedMap()parent中的Map对象传了进去,可以猜想一下,数据传递就发生在这个方法中。现在点进去验证一下,是不是在做数据传递:

private ThreadLocalMap(ThreadLocalMap parentMap) {
	// 通过parent的Map参数来创建新的Map
    Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    setThreshold(len);
    table = new Entry[len];

    for (int j = 0; j < len; j++) {
    	// 将parent的Map中的数据,传递到新的Map中
        Entry e = parentTable[j];
        if (e != null) {
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            // 只有未失效的节点才会做传递的操作
            if (key != null) {
                Object value = key.childValue(e.value);
                Entry c = new Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while (table[h] != null)
                    h = nextIndex(h, len);
                table[h] = c;
                size++;
            }
        }
    }
}

通过上面这种方式,就将父线程的Map中的数据传递到了子线程的Map中,并且由于子线程中的inheritableThreadLocals是一个新的对象,与父线程之间互不影响。

2.2.3.childValue()方法

上面提到的InheritableThreadLocal类中,还有一个childValue()方法,里面的实现就是传入什么就返回什么。我们可以看到,在父子线程做数据传递时使用了这个方法。

Object value = key.childValue(e.value);

为什么在这里不直接使用e.value,要多此一举的去调用一次呢?
在这个方法的注释上面写了:

This method merely returns its input argument, and should be overridden if a different behavior is desired.

意思就是说,这里就是传入什么就返回什么,如果想要这个方法有不同的行为的话,应该去重写这个方法。
childValue()这个方法是protected修饰的,也就是说我们可以自定义一个类去继承InheritableThreadLocal类,然后重写这个方法,在收到父线程传递过来的数据的时候,去做些特殊的操作。
比如对传入的数据做一点修改再返回,或者打印个日志什么的。

public class InheritableThreadLocalExtendDemo<T> extends InheritableThreadLocal<T> {

    static InheritableThreadLocalExtendDemo<String> local = new InheritableThreadLocalExtendDemo<>();

    @Override
    protected T childValue(T parentValue) {
        System.out.println("进入父子线程传递数据方法");
        if (parentValue instanceof String) {
            parentValue = (T) ("姓名:" + parentValue);
        }
        return parentValue;
    }

    public static void main(String[] args) {
        local.set("张三");
        new Thread(() -> System.out.println(local.get())).start();
    }
}

姓名:张三

3.线程池中的线程数据传递失效

通过上面的代码分析可以知道,父子线程的数据传递是在父线程中创建子线程时发生的,但是线程池中的线程是可复用的,这种情况下由于没有新建的动作,数据自然就不会发生传递。

但是如果线程池中的线程数量没有到达最大线程数,那么还是会存在新建线程的动作,这时候传递又是生效的,所以使用线程池的时候,父子线程间的数据传递并不稳定,尽可能的不在线程池中使用InheritableThreadLocal

你可能感兴趣的:(并发编程,java,多线程)