ThreadLocal与InheritableThreadLocal区别

最近项目中为了让打印的日志可以追踪,看到了Logback和log4j2支持的MDC功能,其内部利用了子线程从父线程继承InheritableThreadLocal类型变量的特性。以前只使用到了ThreadLocal,在这里做一下对比,并简单的做了一些分析

ThreadLocal与InheritableThreadLocal区别

ThreadLocal声明的变量是线程私有的成员变量,每个线程都有该变量的副本,线程对变量的修改对其他线程不可见

InheritableThreadLocal声明的变量同样是线程私有的,但是子线程可以使用同样的InheritableThreadLocal类型变量从父线程继承InheritableThreadLocal声明的变量,父线程无法拿到其子线程的。即使可以继承,但是子线程对变量的修改对父线程也是不可见的。

原理

ThreadLocal原理:

Thread有一个类型为ThreadLocal.ThreadLocalMap变量:threadLocals,ThreadLocalMap底层是一个Entry数组,用于保存ThreadLocal引用和真正变量(程序中销售人的主管姓名)的值

看下面的代码和注释了解如何将值放入threadLocals的

调用ThreadLocal对象的set()方法

   public void set(T value) {
        //获取到当前线程对象
        Thread t = Thread.currentThread();
        //从当前线程对象中拿threadLocals变量
        ThreadLocalMap map = getMap(t);
        //如果threadLocals不为空,则调用它的set方法,将key=当前ThreadLocal对象 value=主管姓名
        if (map != null)
            map.set(this, value);
        //如果threadLocals为空,则新创建一个ThreadLocalMap
        else
            createMap(t, value);
    }

InheritableThreadLocal原理:

InheritableThreadLocal继承自ThreadLocal,因此可以想到它自己要么重写,要么新增了部分方法。从源码看只是重写了childValue、getMap、createMap方法,其中getMap和createMap都是父类set方法中使用到的,因此主要是改变了父类set方法的行为。

getMap获取的是InheritableThreadLocal类型的变量

createMap创建的是InheritableThreadLocal类型的实例

但是从这点上看是不能达到子线程继承父线程的变量的。再看看Thread的init方法中比以前新增了如下代码:

//如果当前线程的InheritableThreadLocal类型变量inheritThreadLocals为空且父线程的
//inheritThreadLocals不为空,则复制父线程的inheritThreadLocals中的内容给当前线程的
//inheritThreadLocals,是值复制不是引用赋值
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

上面说到重写了三个方法,其中的childValue还没有找到使用的地方,现在该它出场了,就是在继承变量的时候,创建新的ThreadLocalMap使用到了。

createInheritedMap->ThreadLocalMap->{

//创建一个新的Entry数组

table = new Entry[len];

//复制数据

Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
table[h] = c;

}

测试代码:

ThreadLocal测试

写一个销售人类,该类有一个主管人员成员变量(ThreadLocal声明),将销售人对象传给不同的工作线程,在工作线程中打印销售人的主管,其结果应该是为NULL,因为主管是声明为线程本地变量,是不能共享的

package com.jv.jdk.thread.threadlocal;
​
import lombok.Data;
​
@Data
public class Salesman {
    private ThreadLocal<String> charger = new ThreadLocal<>();
    public Salesman(String charger){
        this.charger.set(charger);
    }
}
package com.jv.jdk.thread.threadlocal;
​
​
import lombok.extern.log4j.Log4j2;   //@Log4j2注解可以很方便做日志输出
​
import java.util.concurrent.CountDownLatch;
​
​
@Log4j2
public class Worker implements Runnable{
    private Salesman salesman = null;
    private CountDownLatch countDownLatch = null;
​
    public Worker(Salesman salesman, CountDownLatch countDownLatch){
        this.salesman = salesman;
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        log.info(Thread.currentThread().getName()+":"+salesman.getCharger().get());
        countDownLatch.countDown();
    }
}

测试类

   @Test
    public void threadLocal(){
        CountDownLatch countDownLatch = new CountDownLatch(2);
        Salesman salesman = new Salesman("刘墙东");
        Thread thread_1 = new Thread(new Worker(salesman,countDownLatch));
        Thread thread_2 = new Thread(new Worker(salesman,countDownLatch));
        thread_1.start();
        thread_2.start();
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

测试结果:

2018.08.30 at 14:47:45 CST 14-Thread-1 INFO  com.jv.jdk.thread.threadlocal.Worker run() @20  - Thread-1:null
2018.08.30 at 14:47:45 CST 15-Thread-2 INFO  com.jv.jdk.thread.threadlocal.Worker run() @20  - Thread-2:null

InheritableThreadLocal测试

与上面的代码重写SalesMan类

package com.jv.jdk.thread.threadlocal;
​
import lombok.Data;
​
@Data
public class SalesmanInherit extends Salesman{
    private InheritableThreadLocal<String> charger = new InheritableThreadLocal<>();
    public SalesmanInherit(String charger){
        super(charger);
        this.charger.set(charger);
    }
}
   @Test
    public void inheritablThreadLocal(){
        CountDownLatch countDownLatch = new CountDownLatch(2);
        Salesman salesman = new Salesman("刘墙东");
        SalesmanInherit salesmanInherit = new SalesmanInherit("刘墙东");
        Thread thread_1 = new Thread(new Worker(salesmanInherit,countDownLatch));
        Thread thread_2 = new Thread(new Worker(salesmanInherit,countDownLatch));
        thread_1.start();
        thread_2.start();
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

测试结果:

2018.08.30 at 14:51:28 CST 15-Thread-2 INFO  com.jv.jdk.thread.threadlocal.Worker run() @20  - Thread-2:刘墙东
2018.08.30 at 14:51:28 CST 14-Thread-1 INFO  com.jv.jdk.thread.threadlocal.Worker run() @20  - Thread-1:刘墙东

猜你喜欢

转载自my.oschina.net/u/3049601/blog/1940723