ThreadLocal和InheritableThreadLocal

ThreadLocal 介绍:

变量值的共享可以使用public static变量的形式使用,所有的线程都可以使用同一个public static变量,那么有没有一个东西能够使每一个线程都拥有自己的变量呢?ThreadLocal就是解决这样的问题的。

ThreadLocal 类提供线程局部变量,它通常是私有类中希望将状态与线程关联的静态字段。

简而言之,就是 ThreadLocal 提供了线程间数据隔离的功能,从它的命名上也能知道这是属于一个线程的本地变量。也就是说,每个线程都会在 ThreadLocal 中保存一份该线程独有的数据,所以它是线程安全的。
我们可以通过一个简单的例子来看看threadlocal的作用和使用方法

public class Demo1 {
    public static void main(String[] args) {
        ThreadLocal threadLocal=new ThreadLocal();
        new Thread(()->{
            for (int i = 1; i <= 3; i++) {
                threadLocal.set("A:"+i);
                System.out.println("A get"+threadLocal.get());
            }
        },"A").start();

        new Thread(()->{
            for (int i = 1; i <= 3; i++) {
                threadLocal.set("B:"+i);
                System.out.println("B get"+threadLocal.get());
            }
        },"B").start();

        for (int i = 1; i <= 3; i++) {
            threadLocal.set("main:"+i);
            System.out.println("main get"+threadLocal.get());
        }
    }
}
测试结果:
B getB:1
A getA:1
A getA:2
A getA:3
B getB:2
B getB:3
main getmain:1
main getmain:2
main getmain:3

从运行结果可以看出不同的线程通过threadlocal可以向每个线程储存自己的私有数据。

ThreadLocal原理

threadlocal会将当前数据放入当前线程对象的Map中这个Map是Thread类的实例变量。threadlocal自己不管理,不储存任何数据,他只是数据和Map之间的一个桥梁,用于将数据放入Map中,执行流程如下图:
在这里插入图片描述
执行后每个线程的Map中存有自己的数据,map中的key储存的是Threadlocal对象value就是储存的值。每个thread中的map只对当前线程可见,其他线程不可以访问该map中的值。当前线程销毁了,map也随之销毁,当前map中的数据如果没有被引用或者没有被使用则会被GC回收。

ThreadLocal存取数据流程分析

下面我们结合上图和UML类图和jdk源码来分析一下ThreadLocal类执行存取数据的操作流程。
在这里插入图片描述
1) 执行threadLocal.set(“A:”+i);其源代码如下

 public void set(T value) {
        Thread t = Thread.currentThread();//main线程
        ThreadLocalMap map = getMap(t);//从main线程中获得threadlocalmap对象
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);//第一次调用set方法时调用执行
        }
    }

2)ThreadLocalMap map = getMap(t);其源码如下

 ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
        //返回main线程中threadlocals变量对应的threadlocalMap对象
    }

该源代码在Thread类中

 ThreadLocal.ThreadLocalMap threadLocals = null;
 //threadlocals数据类型是ThreadLocal.ThreadLocalMap

3)取得thread中的ThreadLocal.ThreadLocalMap后,第一次向其中存放数据会调用createMap(t, value)方法来创建ThreadLocal.ThreadLocalMap对象,默认值是null创建时key就是当前的ThreadLocal对象值就是传入的value。

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

参数this是当前的threadlocal对象,firstValue就是调用Threadlocal对象set方法传入的参数值。
4) new ThreadLocalMap(this, firstValue)构造方法源码如下:

 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

从源代码可以看出threadlocal对象和firstValue被封装进Entry对象并放入table[]数组中。
5)table[]数组的源代码如下:
在这里插入图片描述
ThreadLocalMap类中table是一个Entry[]数组类型。
Entry类的源码如下:该类就是用来存放threadlocal对象和value值

 static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

经过上面几个步骤成功将value通过ThreadLocal放入当前线程中的ThreadLocalMap对象中。
下面我们来看看get()方法的执行过程,其源码如下:

 public T get() {
        Thread t = Thread.currentThread();//t就是main线程
        ThreadLocalMap map = getMap(t);//从main线程中获得map
        if (map != null) {
        //以this为key获得对应的Entry对象
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;//从Entry对象中获得value值并返回
                return result;
            }
        }
        return setInitialValue();
    }

看完源代码后你可能会有一个疑问为什么不直接向Thread类中的ThreaLocalMap中直接存取数据?其原因是因为
ThreadLocal.ThreadLocalMap threadLocals = null; 变量threadLocals
是包级访问权限,所以不能直接从外部访问该变量只有通过同包中的类来访问,而恰好ThreadLocal和Thread在同一个包中。

重写initialValue()方法实现隔离性

在第一次调用get方法返回值是null可以通过重写initialValue()方法实现返回默认值。

public class Demo2 {
    public static void main(String[] args) throws InterruptedException{
        new Thread(()->{
            for (int i = 1; i <= 3; i++) {
                System.out.println("B get"+Tools.getThreadLocal().get());
            }
        },"B").start();

        TimeUnit.SECONDS.sleep(2);
        
        for (int i = 1; i <= 3; i++) {
            System.out.println("main get"+Tools.getThreadLocal().get());
        }
    }
}
class Tools extends ThreadLocal{
    @Override
    protected Object initialValue() {
        return new Date().getTime();
    }
    public static Tools getThreadLocal(){
        return new Tools();
    }
}

测试结果:
B get1619784730996
B get1619784731026
B get1619784731026
main get1619784732996
main get1619784732996
main get1619784732996

InheritableThreadLocal类的使用

类ThreadLocal不能实现值继承

public class Demo3 {
    public static void main(String[] args) throws InterruptedException{
        ThreadLocal threadLocal = new ThreadLocal();

        for (int i = 1; i <= 3; i++) {
            threadLocal.set("main set value:"+i);
            System.out.println("main get"+threadLocal.get());
        }

        TimeUnit.SECONDS.sleep(2);


        new Thread(()->{
            for (int i = 1; i <= 3; i++) {
                System.out.println("A get:"+threadLocal.get());
            }
        },"A").start();

    }
}
测试结果:
main getmain set value:1
main getmain set value:2
main getmain set value:3
A get:null
A get:null
A get:null

main线程创建了A线程所以main线程是A线程的父线程。从运行结果来看可以发现main线程中的值并没有继承给A线程,所以ThreadLocal并没有值继承这一特性。

使用InheritableThreadLocal类实现值继承

public class Demo4 {
    public static void main(String[] args) throws InterruptedException{
        InheritableThreadLocal threadLocal = new InheritableThreadLocal();

        for (int i = 1; i <= 3; i++) {
            threadLocal.set("main set value");
            System.out.println("main get"+threadLocal.get());
        }

        TimeUnit.SECONDS.sleep(2);
        new Thread(()->{
            for (int i = 1; i <= 3; i++) {
                System.out.println("A get:"+threadLocal.get());
            }
        },"A").start();
    }
}
测试结果:
main getmain set value
main getmain set value
main getmain set value
A get:main set value
A get:main set value
A get:main set value

InheritableThreadLocal存取数据流程分析

使用InheritableThreadLocal类确实可以实现值继承特性下面我们来分析一下在jdk中的源码:
1)InheritableThreadLocal类的源代码如下
在这里插入图片描述
这三个方法都是对父类的方法进行重写。
2)执行threadLocal.set(“main set value”)方法其实就是调用父类的set方法,因为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);
        }
    }

不过getMap和createMap方法都被重写了所以调用的是InheritableThreadLocal类的方法。
3)getMap方法源码如下
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
可以发现InheritableThreadLocal类是向ThreadLocal.ThreadLocalMap inheritableThreadLocals存放数据。
4)子线程如何从父线程中继承到值的呢?源代码如下
在这里插入图片描述

private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (Entry e : parentTable) {
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    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++;
                    }
                }
            }
        }

ThreadLocal应用场景

1)解决SimpleDateFormat多线程下异常问题
正常多线程情况不使用ThreadLocal

public class Demo5 {
    public static void main(String[] args) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String[] dates = new String[]{"2021-01-01", "2021-04-01", "2021-02-01", "2021-03-01", "2021-01-11"};
        MyThread[] threads = new MyThread[10];
        for (int i = 0; i < 5; i++) {
            threads[i] = new MyThread(simpleDateFormat, dates[i]);
            threads[i].start();
        }
    }
}
class MyThread extends Thread{
    private SimpleDateFormat simpleDateFormat;
    private String date;

    public MyThread(SimpleDateFormat simpleDateFormat, String date) {
        this.simpleDateFormat = simpleDateFormat;
        this.date = date;
    }

    @Override
    public void run() {
        try {
            Date time = simpleDateFormat.parse(date);
            String newTime = simpleDateFormat.format(time).toString();
            if (!newTime.equals(time)) {
                System.out.println("threadName:"+this.getName()
                +"出错了  日期字符串"+time+"转换后的日期:"+newTime);
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
}
测试结果:
Exception in thread "Thread-4" java.lang.NumberFormatException: For input string: "E.21204"
	at java.base/jdk.internal.math.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2054)
	at java.base/jdk.internal.math.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
threadName:Thread-1出错了  日期字符串Wed Jan 01 00:00:00 CST 2200转换后的日期:2200-01-01
threadName:Thread-0出错了  日期字符串Wed Jan 01 00:00:00 CST 2200转换后的日期:2200-01-01
threadName:Thread-3出错了  日期字符串Mon Mar 01 00:00:00 CST 2021转换后的日期:2200-01-01
threadName:Thread-2出错了  日期字符串Wed Mar 01 00:00:00 CST 2209转换后的日期:2209-03-01

日期转化错误。
下面通过使用ThreadLocal来处理异常,添加工具类

public class Demo5 {
    public static void main(String[] args) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String[] dates = new String[]{"2021-01-01", "2021-04-01", "2021-02-01", "2021-03-01", "2021-01-11"};
        MyThread[] threads = new MyThread[10];
        for (int i = 0; i < 5; i++) {
            threads[i] = new MyThread(simpleDateFormat, dates[i]);
            threads[i].start();
        }
    }
}
class MyThread extends Thread{
    private SimpleDateFormat simpleDateFormat;
    private String date;

    public MyThread(SimpleDateFormat simpleDateFormat, String date) {
        this.simpleDateFormat = simpleDateFormat;
        this.date = date;
    }

    @Override
    public void run() {
        try {
            Date time = DateTools.getSimpleDateFormat("yyyy-MM-dd").parse(date);
            String newTime = DateTools.getSimpleDateFormat("yyyy-MM-dd").format(time).toString();
            if (!newTime.equals(time)) {
                System.out.println("threadName:"+this.getName()
                +"原来日期字符串"+time+"转换后的日期:"+newTime);
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
}
class DateTools{
    private static ThreadLocal<SimpleDateFormat> threadLocal=new ThreadLocal<>();
    public static SimpleDateFormat getSimpleDateFormat(String datePattern){
        SimpleDateFormat simpleDateFormat=null;
        simpleDateFormat=threadLocal.get();
        if (simpleDateFormat == null) {
            simpleDateFormat=new SimpleDateFormat(datePattern);
            threadLocal.set(simpleDateFormat);
        }
        return simpleDateFormat;
    }
}
测试结果:
threadName:Thread-1原来日期字符串Thu Apr 01 00:00:00 CST 2021转换后的日期:2021-04-01
threadName:Thread-3原来日期字符串Mon Mar 01 00:00:00 CST 2021转换后的日期:2021-03-01
threadName:Thread-2原来日期字符串Mon Feb 01 00:00:00 CST 2021转换后的日期:2021-02-01
threadName:Thread-0原来日期字符串Fri Jan 01 00:00:00 CST 2021转换后的日期:2021-01-01
threadName:Thread-4原来日期字符串Mon Jan 11 00:00:00 CST 2021转换后的日期:2021-01-11

结果正常。
2)Spring实现事务隔离级别
Spring采用Threadlocal的方式,来保证单个线程中的数据库操作使用的是同一个数据库连接,同时,采用这种方式可以使业务层使用事务时不需要感知并管理connection对象,通过传播级别,巧妙地管理多个事务配置之间的切换,挂起和恢复。

Spring框架里面就是用的ThreadLocal来实现这种隔离,主要是在TransactionSynchronizationManager这个类里面,代码如下所示:

private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);

	private static final ThreadLocal<Map<Object, Object>> resources =
			new NamedThreadLocal<>("Transactional resources");

	private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
			new NamedThreadLocal<>("Transaction synchronizations");

	private static final ThreadLocal<String> currentTransactionName =
			new NamedThreadLocal<>("Current transaction name");

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

活跃的咸鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值