1.背景

    ThreadLocal类我想一般的码农或初级程序员在平时开发中基本上接触不到,但是面试老师会问、往高级点走会遇到这个类。这个类不是为了解决资源的竞争问题,而是为每个线程提供同一个容器,容器内部为每个线程提供一块空间,各个线程只能操作自己的空间,相互之间独立。

    这个类在java.lang包中。

2.使用示例

    废话不多说,直接先看一下简单使用demo

public class ThreadLocalTest {

        private static ThreadLocal<List<String>> threadLocal = new ThreadLocal<>();

    public void setThreadLocal(List<String> value){
threadLocal.set(value);
}
public void getThreadLocal(){
List<String> strings = threadLocal.get();
strings.forEach(value -> System.out.println(Thread.currentThread().getName() + "###" + value));
} public static void main(String[] args){
System.out.println("test threadlocal");
ThreadLocalTest threadLoaclTest = new ThreadLocalTest();
new Thread(new Runnable() {
@Override
public void run() {
ArrayList<String> strings = new ArrayList<>();
strings.add("1");
strings.add("2");
strings.add("3");
threadLoaclTest.setThreadLocal(strings);
threadLoaclTest.getThreadLocal();
}
},"t1").start(); new Thread(new Runnable() {
@Override
public void run() {
ArrayList<String> strings = new ArrayList<>();
strings.add("a");
strings.add("b");
strings.add("c");
threadLoaclTest.setThreadLocal(strings);
threadLoaclTest.getThreadLocal();
}
},"t2").start();
}
//ThreadLocal类型变量表面被多个线程使用,每个线程设置互相之间没有影响。因为它内部有一个ThreadLocalMap类型的变量映射了线程和它所设定的值。
/*
执行结果
t1###1
t1###2
t1###3
t2###a
t2###b
t2###c
*/
}
3.一些和threadlocal相关的面试总结,参考https://www.jianshu.com/p/3f0f9194d658
11)什么是线程局部变量?

当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,每个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本,是线程隔离的。线程隔离的秘密在于ThreadLocalMap类(ThreadLocal的静态内部类)

线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java 提供 ThreadLocal 类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。

ThreadLocal的方法:void set(T value)、T get()以及T initialValue()。

ThreadLocal是如何为每个线程创建变量的副本的:

首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

总结:

a、实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的

b、为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;

c、在进行get之前,必须先set,否则会报空指针异常;如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法(这一条是错误的,不会报错,而是返回null)
4.简单看一下源码
ThreadLocal源码分析

    1.public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); //1.获取当前线程的ThreadLocalMap类型成员变量
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); //2.这个map中可能存放多个ThreadLocal类型变量,map存放key就是ThreadLocal对象本身,而value就是ThreadLocal<T>的T类型数值
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
} ThreadLocalMap getMap(Thread t) {
return t.threadLocals; //3.每个线程对象中的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;
} protected T initialValue() {
return null; //4.如果在set前,get的话得到的就是null
} void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
} 2.public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
} 3.public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
} //所以提到ThreadLocal类,在源码层面应该立即想到:在Thread类中有个ThreadLocal.ThreadLocalMap类型的变量threadlocals.
5.ThreadLocal的应用场景

这是最重要的,非常重要的,学的再好,不会用,甚至不知道用在哪,学来何用,不就是浪费精力吗?前面说过对于码农而言,在实际开发过程中不容易用到这个东西,如果你能清楚知道它用在什么地方,那么是不是就高级一点,这就是可以提高的地方。

1.spring对有状态bean的管理
Spring使用ThreadLocal解决线程安全问题。通常只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全的“状态性对象”采用ThreadLocal进行封装,让它们也成为线程安全的“状态性对象”,因此有状态的Bean就能够以singleton的方式在多线程中正常工作了。参考:https://www.jianshu.com/p/f956857a8304

java核心-多线程(9)- ThreadLocal类的更多相关文章

  1. java核心-多线程(4)-线程类基础知识

    1.并发 <1>使用并发的一个重要原因是提高执行效率.由于I/O等情况阻塞,单个任务并不能充分利用CPU时间.所以在单处理器的机器上也应该使用并发. <2>为了实现并发,操作系 ...

  2. Java并发多线程 - 并发工具类JUC

    安全共享对象策略 1.线程限制 : 一个被线程限制的对象,由线程独占,并且只能被占有它的线程修改 2.共享只读 : 一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问, 但是任何线程都 ...

  3. java核心-多线程-Java多线程编程涉及到包、类

    Java有关多线程编程设计的类主要涉及两个包java.lang和java.util.concurrent两个包 java.lang包,主要是线程基础类 <1>Thread <2> ...

  4. Java并发编程之ThreadLocal类

    ThreadLocal类可以理解为ThreadLocalVariable(线程局部变量),提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回当 ...

  5. 编写Java程序,使用ThreadLocal类,项目中创建账户类 Account,类中包括账户名称name、 ThreadLocal 类的引用变量amount,表示存款

    查看本章节 查看作业目录 需求说明: 某用户共有两张银行卡,账户名称相同,但卡号和余额不同.模拟用户使用这两张银行卡进行消费的过程,并打印出消费明细 实现思路: 项目中创建账户类 Account,类中 ...

  6. Java并发基础07. ThreadLocal类以及应用技巧

    在前面的文章(6. 线程范围内共享数据)总结了一下,线程范围内的数据共享问题,即定义一个 Map,将当前线程名称和线程中的数据以键值对的形式存到 Map 中,然后在当前线程中使用数据的时候就可以根据当 ...

  7. java核心-多线程-线程类-Callable、Future和FutureTask

    基本概念 <1>Callable,Callable和Runnable差不多,两者都是为那些其实例可能被另一个线程执行的类而设计的,最主要的差别在于Runnable不会 返回线程运算结果,C ...

  8. java核心-多线程(6)-线程池-ThreadPoolExecutor

    1.java多线程编程少不了使用线程池,线程池相关的工具类所在jdk包,java.util.concurrent 2.使用示例 demo1 public class ThreadPoolDemo { ...

  9. java核心-多线程(8)- 并发原子类

        使用锁能解决并发时线程安全性,但锁的代价比较大,而且降低性能.有些时候可以使用原子类(juc-atomic包中的原子类).还有一些其他的非加锁式并发处理方式,我写这篇文章来源于Java中有哪些 ...

随机推荐

  1. centOS+DJango+mysql_nginx部署流程记录

    安装Python3.6.2: https://www.jianshu.com/p/7a76bcc401a1 安装MySQL: https://www.cnblogs.com/luohanguo/p/9 ...

  2. 报错PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target"

    今天在调用第三方HTTPS接口的时候,一直显示这个报错,然后百度很久,有2种解决方法,一个是说自己手动去导入,第二种用代码忽略证书验证.我用二种方式, 复制即用, public void test2( ...

  3. Ubuntu12.04LTS中安装和使用Spin

    https://blog.csdn.net/jackandsnow/article/details/94434481 把第三步 安装tk(在wish中):apt-get install wish 改为 ...

  4. CSP2019 括号树

    Description: 给定括号树,每个节点都是 ( 或 ) ,定义节点的权值为根到该节点的简单路径所构成的括号序列中不同合法子串的个数(子串需要连续,子串所在的位置不同即为不同.)与节点编号的乘积 ...

  5. SRS源码—— Thread笔记

    SRS源码中的Thread是一层套一层,最终的Thread类是在 srs_app_thread.cpp 的 SrsThread 类 这里我们暂且先放下协程的概念,把它当线程来看,其逻辑如下: 1. 在 ...

  6. rarlinux安装和使用

    rarlinux安装和使用

  7. 【html&css学习】表单及表单项

    表单在网络中很常见,如百度的搜索框,各种登录框密码,贴吧的帖子等都需要用表单来完成.表单是元素form且必须要有action属性来设置表单提交的地址.使用form创建的仅仅只是空表单,还有要表单项,常 ...

  8. Day11 - L - 邂逅明下 HDU - 2897

    当日遇到月,于是有了明.当我遇到了你,便成了侣.那天,日月相会,我见到了你.而且,大地失去了光辉,你我是否成侣?这注定是个凄美的故事.(以上是废话)小t和所有世俗的人们一样,期待那百年难遇的日食.驻足 ...

  9. Django下关于session的使用

    一.Session的概念 cookie是在浏览器端保存键值对数据,而session是在服务器端保存键值对数据 session 的使用依赖 cookie:在使用Session后,会在Cookie中存储一 ...

  10. 高手教大家如何配置JVM参数

    /usr/local/jdk/bin/java -Dresin.home=/usr/local/resin -server -Xms1800M -Xmx1800M -Xmn300M -Xss512K ...