文档章节

Java 多线程:ThreadLocal关键字

i
 icanos
发布于 2016/07/21 20:05
字数 1462
阅读 47
收藏 10

什么是ThreadLocal


ThreadLocal并非一个线程,而是一个线程局部变量。它的作用就是为使用该变量的线程都提供一个变量值的副本,每个线程都可以独立的改变自己的副本,而不会和其他线程的副本造成冲突。

从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。

通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。

概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

实际运用中使用的 ThreadLocal


那么,在实际运用中,有这样的需求:要得到当前用户。当然,方法有很多,比如说session,cookie 或者什么的。对于本人来说,由于使用了 OAuth,每个用户都会有一个 Access Token。token 的意思就是 代表了该用户有没有权限访问后台系统,是唯一的。那么,此时就可以根据这个 token 来得到当前用户。

具体做法: 在生成token的时候,会保存一个关联,即用户-token 关联。那么在用户访问的时候,就可以使用拦截器得到该用户的 token,然后查找关联表,从而得到用户,最后再放入到 ThreadLocal 变量中,以供后续步骤可以拿到。

关键代码如下:

拦截器中得到 token,查找用户:

String accessToken = httpServletRequest.getHeader("Authorization");
if(!StringUtils.isEmpty(accessToken)){
    String token[] = accessToken.split(" ");

    Staff staff = staffService.findStaffByAccessToken(token[1]);
    if(staff != null){
        StaffUtils.staffHolder.set(staff);
        log.debug("CurrentStaffFilter doFilter function ...access_token = {}. and staff in the threadlocal = {}.",accessToken,StaffUtils.staffHolder.get());
    } else {
        log.debug("CurrentStaffFilter doFilter function ...access_token = {}. But staff is null",accessToken);
    }
} else {
    log.debug("CurrentStaffFilter doFilter function ...access_token is null");
}

//该 filter 不影响正常调用,所以最后放行
chain.doFilter(request, response);

可以得到,可以首先得到 token, 然后找出 staff,最后进行

StaffUtils.staffHolder.set(staff);

也就是把 staff 放入到 ThreadLocal中, StaffUtils 代码如下:

public class StaffUtils {
    public static ThreadLocal<Staff> staffHolder = new ThreadLocal<Staff>();

    public static Staff getCurrentStaff(){
        return staffHolder.get();
    }
}

这样,在后续的代码中,就可以直接调用 getCurrentStaff() 得到当前用户。因为是在同一个线程中

ThreadLocal 原理


我们从我们怎么用,一步一步分析原理。首先,我们会定义一个 ThreadLocal,如下代码:

public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();

然后,我们会把我们的值放入到 ThreadLocal 中,如下代码:

threadLocal.set(new Integer(123));

这里就表示了 这个 123 的 Integer 对象就和我们的现在这个线程关联起来了,以后我们要使用这个 123 的 Integer 对象,那么我们直接使用如下代码即可返回 123 的 Integer 对象:

threadLocal.get()

set()

那么,在 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);
}

这里,首先 通过 Thread.currentThread() 拿到当前线程,然后 通过 getMap(t) 方法拿到一个 ThreadLocalMap 类型的 map 对象。

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

这里你可能会疑问,threadLocals 是什么,ThreadLocalMap 是什么?

由于 t.threadLocals 所以我们知道 threadLocals 是线程 Thread 类的一个属性,这个属性是 ThreadLocalMap类型的。

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    private Entry[] table;
    ......

这里我们可以知道,每个线程都有一个 ThreadLocalMap 类型的属性 threadLocals,这个 Map 中的 entry 是一个弱引用类型,key 相当于是一个 ThreadLocal 变量,value 就是我们要存储的值。

所以,当我们 调用 threadLocal.set(new Integer(123)); 的时候,首先会得到 该线程 的 threadLocals,然后就会把 该 threadlocal 和 值123 作为一个键值对,放入到该线程的 threadLocals 中。

get()

那么,当我们 get 的时候,发生了什么:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

首先,我们会通过 getMap() 得到 上文 set() 部分 说的 threadLocals。

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

之前我们已经说了,threadLocals 保存了一个 key 是 threadlocal,value 是我们的值 的这样一个键值对。 那么,我们现在首先得到 该线程的 threadLocals,然后,当我们调用 threadlocal.get() 我们就可以根据这个 threadlocal 得到 我们的值,因为 之前我们 set 的时候,key 就是 这个 threadlocal。

就这样一个 get set,过程,就完成了 线程-值的关联。

总结


每个线程都有一个 ThreadLocalMap 类型的 threadLocals 属性。 ThreadLocalMap 类相当于一个Map,key 是 ThreadLocal 本身,value 就是我们的值。 当我们通过 threadLocal.set(new Integer(123)); ,我们就会在这个线程中的 threadLocals 属性中放入一个键值对,key 是 这个 threadLocal.set(new Integer(123)); 的 threadlocal,value 就是值。 当我们通过 threadlocal.get() 方法的时候,首先会根据这个线程得到这个线程的 threadLocals 属性,然后由于这个属性放的是键值对,我们就可以根据键 threadlocal 拿到值。 注意,这时候这个键 threadlocal 和 我们 set 方法的时候的那个键 threadlocal 是一样的,所以我们能够拿到相同的值。 这个就是 Threadlocal 的原理。

本文转载自:https://github.com/pzxwhc/MineKnowContainer/issues/12

i
粉丝 0
博文 24
码字总数 1388
作品 0
深圳
程序员
私信 提问
Java多线程学习(五)线程间通信知识点补充

系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Ja...

一只蜗牛呀
2018/04/16
0
0
Java多线程学习(四)等待/通知(wait/notify)机制

系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Ja...

一只蜗牛呀
2018/04/16
0
0
Java多线程学习(二)synchronized关键字(2)

系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Ja...

一只蜗牛呀
2018/04/16
0
0
再有人问你Java内存模型是什么,就把这篇文章发给他!

前几天,发了一篇文章,介绍了一下JVM内存结构、Java内存模型以及Java对象模型之间的区别。有很多小伙伴反馈希望可以深入的讲解下每个知识点。Java内存模型,是这三个知识点当中最晦涩难懂的...

技术小能手
2018/09/30
0
0
来,了解一下Java内存模型(JMM)

网上有很多关于Java内存模型的文章,在《深入理解Java虚拟机》和《Java并发编程的艺术》等书中也都有关于这个知识点的介绍。但是,很多人读完之后还是搞不清楚,甚至有的人说自己更懵了。本文...

android 开发
2018/08/21
0
0

没有更多内容

加载失败,请刷新页面

加载更多

AOP的学习(1)

AOP 理解AOP编程思想(面向方法、面向切面) spring AOP的概念 方面 -- 功能 目标 -- 原有方法 通知 -- 对原有方法增强的方法 连接点 -- 可以用来连接通知的地方(方法) 切入点 -- 将用来插入...

太猪-YJ
19分钟前
2
0
一张图看懂亮度、明度、光度、光亮度、明亮度

亮度、明度、光亮度,Luminance和Brightness、lightness其实都是一个意思,只是起名字太难了。 提出一个颜色模型后,由于明度的取值与别人的不同,为了表示区别所以就另想一个词而已。 因此在...

linsk1998
昨天
1
0
Python应用:python链表示例

前言 python链表应用源码示例,需要用到python os模块方法、函数和类的应用。 首先,先简单的来了解下什么是链表?链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是...

python小白1
昨天
3
0
Source Insight加载源码

Source Insight是一个图形化的源代码查看工具(当然也可以作为编译工具)。如果一个项目的源代码较多,此工具可以很方便地查找到源代码自建的依赖关系。 1.创建工程 下图为Snort源代码的文件...

天王盖地虎626
昨天
3
0
nginx-rtmp-module的缺陷分析(二)

nginx-rtmp-module使用指令push和pull来relay媒体流数据,以便分布式部署服务。 当nginx-rtmp-module作为边缘服务器(一般不会向边缘服务器推流)时,使用pull从源服务器获取媒体流数据,俗称...

YoungSagit
昨天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部