ThreadLocal设计模式

Easter79
• 阅读 771

ThreadLocal设计模式使用的也很频繁,会经常在各大框架找到它们的踪影,如struts2以及最近正在看的SpringAOP等。

ThreadLocal设计模式也有很多误解,我的理解是
(1)ThreadLocal所操作的数据是线程间不共享的。它不是用来解决多个线程竞争同一资源的多线程问题。
(2)ThreadLocal所操作的数据主要用于线程内共享数据,可以避免同一线程内函数间的传参数问题。
ThreadLocal更像是一个操作线程数据的工具类,哪个线程调用它,它就操作哪个线程的数据。

案例如下:
案例一:ThreadLocal所操作的数据是线程间不共享的,是线程间互不干扰的

package com.lg.design.threadlocal;

public class ThreadLocalTest {

    public static void main(String[] args){
        test1();
    }
    private static void test1(){
        Thread t1=new Thread(){

            @Override
            public void run() {
                System.out.println(this.getName()+"开始值:"+ThreadUtil.getName());
                ThreadUtil.setName("线程0");
                sleepTime(1000*10);
                System.out.println(this.getName()+"设定后:"+ThreadUtil.getName());
            }
            
        };
        Thread t2=new Thread(){

            @Override
            public void run() {
                sleepTime(1000*5);
                System.out.println(this.getName()+"开始值:"+ThreadUtil.getName());
                ThreadUtil.setName("线程1");
                System.out.println(this.getName()+"设定后:"+ThreadUtil.getName());
            }
            
        };
        t1.start();
        t2.start();
    }
    
    private static void sleepTime(long millis){
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

其中ThreadUtil就是用ThreadLocal来实现的,ThreadLocal完全可以这样理解,它就是操作线程数据的工具类,哪个线程调用它的get或set方法,它就会操作调用它的线程中的数据,待会给出源码说明。如下:

public class ThreadUtil {

    private static ThreadLocal<String> nameLocal=new ThreadLocal<String>();
    
    public static String getName(){
        return nameLocal.get();
    }
    
    public static void setName(String name){
        nameLocal.set(name);
    }
}

运行结果为:

Thread-0开始值:null
Thread-1开始值:null
Thread-1设定后:线程1
Thread-0设定后:线程0

分析过程如下:
(1)暂时可以理解为每个线程都有一个map集合,用于存放数据。首先是t1线程调用ThreadUtil.getName(),ThreadUtil.getName()即nameLocal.get()会去调用它的线程的map集合中取数据,即会从t1线程中的map集合中取数据,key为nameLocal对象。由于之前未存数据所以返回为null。
(2)紧接着t1调用ThreadUtil.setName("线程0")即nameLocal.set(name)。此时调用nameLocal.set(name)方法的线程是t1,所以nameLocal仍然是将数据存进t1的map集合中,key为nameLocal对象,然后t1睡眠10s。
(3)t2刚开始睡眠5s,然后调用ThreadUtil.getName()即nameLocal.get(),nameLocal会去调用它的线程的map集合中取数据,即从t2线程的map集合中取数据,由于之前未存,所以仍然为null。
(4)t2开始调用ThreadUtil.setName("线程1")即nameLocal.setName("线程1");,nameLocal仍然将这个值设置到调用它的线程的map集合中,即会将这个数据放到t2线程的map集合中,key为nameLocal对象。
(5)然后是t2线程调用ThreadUtil.getName(),即nameLocal会从t2线程的map集合中取数据,key为nameLocal对象,然后就取到了步骤4所设置的"线程1"。
(6)然后是t1线程调用ThreadUtil.getName(),即nameLocal会从t1线程的map集合中取数据,key为nameLocal对象,然后就取到了步骤2所设置的"线程0"。
上面一直在强调ThreadLocal对象一直是在操作调用它的线程的map数据,谁调用ThreadLocal对象的get或set方法,ThreadLocal对象就会去操作调用它的线程的map数据。这个概念一定要根深蒂固。

从上面的过程中,可以看到,ThreadLocal对象所操作的数据是线程所独有的数据,虽然两个线程使用的是同一个ThreadLocal对象,但ThreadLocal对象所操作的数据是线程间根本不共享的,互不干扰的。所以ThreadLocal模式就不是用于多个线程竞争同一资源的情况。

案例二:
ThreadLocal的作用:用于实现线程内共享数据,注意是线程内共享数据,不是线程间共享数据。

public class ThreadLocalTest {

    public static void main(String[] args){
        test2();
    }
    
    private static void test2(){
        Thread t1=new Thread(){

            @Override
            public void run() {
                fun1();
                fun2();
            }

            private void fun2() {
                System.out.println("在函数2中获取到当前线程的name数据为:"+ThreadUtil.getName());
            }

            private void fun1() {
                ThreadUtil.setName("my own name");
                System.out.println("函数1设置当前线程的name数据为:my own name");
            }
            
        };
        t1.start();
    }
}

运行结果为:

函数1设置当前线程的name数据为:my own name
在函数2中获取到当前线程的name数据为:my own name

分析如下:
(1)fun1()和fun2()都在t1线程内执行,在fun1中调用ThreadUtil.setName("my own name"),则是向调用它的t1线程的map集合中存放数据,key为nameLocal。
(2)fun2函数调用ThreadUtil.getName(),则是从调用它的t1线程的map集合中获取数据,key为nameLocal,所以就可以取到fun1中设置的数据

综上所述,通过ThreadLocal对象,把线程内要共享的数据存进该线程的map集合中,方便随时获取,实现共享。不然的话,要想实现数据的共享,则需要fun1将数据作为参数传给fun2,这个例子太简单,然而实际情况是某个线程中几十个函数嵌套调用,如果不将线程内共享数据放进该线程的map集合中,而是将共享数据作为参数传递来传递去,将会一片混乱,杂乱无章,有些函数需要这些参数,有些函数不需要。采用ThreadLocal模式,将数据放进线程的map集合中,想要获取时随时可以通过ThreadLocal对象来获取,方便快捷。

这就是ThreadLocal模式。下面源码来说明,ThreadLocal对象操作的是调用它的线程。
这就需要看下它的get和set方法:

public T get() {
      //重点1
        Thread t = Thread.currentThread();
     //重点2
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //重点3
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
      //重点4
        return setInitialValue();
    }

(1)首先就是获取当前调用ThreadLocal对象的线程。如t1线程调用ThreadLocal对象的get方法,此时的Thread t就是t1.
(2)Thread类拥有这样一个属性 ThreadLocal.ThreadLocalMap threadLocals = null;
getMap(t)就是获取t线程的threadLocals,如下:

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

当线程中的ThreadLocal.ThreadLocalMap threadLocals不为空,则以this即当前的ThreadLocal对象作为key来获取对应的value。
当线程中的ThreadLocal.ThreadLocalMap threadLocals为空时,则会初始化:

private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

通过initialValue()来获取初始值,默认是null,这个过程也很清晰明了。就是判断当前线程中ThreadLocalMap是否为空,若为空则创建一个,同时将初始值设置进ThreadLocalMap集合中,key就是ThreadLocal对象本身。其中的initialValue()如下:

protected T initialValue() {
        return null;
    }

即我们可以重写该方法来设置一个初始值。

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

也很清晰明了,就是获取当期线程的ThreadLocalMap,然后向其中存放数据,key为ThreadLocal对象本身。

这就是ThreadLocal设计模式。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
ThreadLocal源码解析及实战应用
ThreadLocal是一个关于创建线程局部变量的类。通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。ThreadLocal在设计之初就是为解决并发问题而提供一种方案,每个线程维护一份自己的数据,达到线程隔离的效果。
Easter79 Easter79
3年前
TransmittableThreadLocal在使用线程池等会缓存线程的组件情况下传递ThreadLocal
1、简介TransmittableThreadLocal是Alibaba开源的、用于解决“在使用线程池等会缓存线程的组件情况下传递ThreadLocal”问题的InheritableThreadLocal扩展。若希望TransmittableThreadLocal在线程池与主线程间传递,需配合_TtlRunnab
Wesley13 Wesley13
3年前
Java多线程与并发之ThreadLocal原理解析
1\.ThreadLocal是什么?使用场景ThreadLocal简介ThreadLocal是线程本地变量,可以为多线程的并发问题提供一种解决方式,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,
Wesley13 Wesley13
3年前
Java多线程与并发之ThreadLocal
1\.ThreadLocal是什么?使用场景ThreadLocal简介ThreadLocal是线程本地变量,可以为多线程的并发问题提供一种解决方式,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,
Easter79 Easter79
3年前
ThreadLocal 原理和使用场景分析
ThreadLocal不知道大家有没有用过,但至少听说过,今天主要记录一下ThreadLocal的原理和使用场景。使用场景直接定位到ThreadLocal的源码,可以看到源码注释中有很清楚的解释:它是线程的局部变量,这些变量只能在这个线程内被读写,在其他线程内是无法访问的。ThreadLocal定义的通常是
Wesley13 Wesley13
3年前
JAVA基础系列:ThreadLocal
1. 思路1.什么是ThreadLocal?ThreadLocal类顾名思义可以理解为线程本地变量。也就是说如果定义了一个ThreadLocal,每个线程往这个ThreadLocal中读写是线程隔离,互相之间不会影响的。它提供了一种将可变数据通过每个线程有自己的独立副本从而实现线程封闭的机制。2.它大致的实现
Easter79 Easter79
3年前
ThreadLocal理解
1.使用ThreadLocal的时候我们保证了每个线程可以隔离使用对象,避免线程间的数据干扰。常用例子:publicclassThreadLocalTest{publicstaticvoidmain(Stringargs)throwsInterruptedException{
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k