类ThreadLocal的使用与源码分析
变量值的共享可以使用public static的形式,所有的线程都使用同一个变量。如果每个线程都有自己的共享变量,就可以使用ThreadLocal。比如Hibernat的session问题就是存在ThreadLoca中。
类ThreadLocal主要解决的就是每个线程绑定自己的值,可以将ThreadLocal比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。
而且ThreadLocal一般用作静态成员变量封装在工具类中实现线程隔离数据。在JavaEE结构中就是从Action层到Dao层可以使用threadLocal实现共享数据,并且线程之间相互隔离。(对于ThreadLocal,每个线程存进去的东西与取出来的是一致的,不会出现相互覆盖的现象。)
ThreadLocal就是,把一个数据复制N份,每个线程认领一份,各玩各的,互不影响。
1. 方法 get()与null
ThreadLocal的基本使用方法。
package cn.qlq.thread.ten; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* ThreadLocal的基本使用
*
* @author QiaoLiQiang
* @time 2018年12月15日下午9:00:19
*/
public class Demo1 {
public static ThreadLocal<String> t1 = new ThreadLocal<String>();
private static final Logger LOGGER = LoggerFactory.getLogger(Demo1.class); public static void main(String[] args) {
if (t1.get() == null) {
LOGGER.info("从未放过值");
t1.set("存放的值");
}
LOGGER.info("{}", t1.get());
LOGGER.info("{}", t1.get());
}
}
结果:
21:02:38 [cn.qlq.thread.ten.Demo1]-[INFO] 从未放过值
21:02:38 [cn.qlq.thread.ten.Demo1]-[INFO] 存放的值
21:02:38 [cn.qlq.thread.ten.Demo1]-[INFO] 存放的值
从第一个的返回结果看,第一次调用t1对象的get()方法时返回的值是null,通过set()赋值之后可以取出值。类ThreadLocal解决的是变量在不同线程间的隔离性,也就是每个线程拥有自己的值,不同线程中的值是可以放入ThreadLocal类中进行保存的。
2.验证变量间的隔离性
验证其隔离性,每个线程在变量间存放的值不同。
package cn.qlq.thread.ten; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* ThreadLocal的基本使用
*
* @author QiaoLiQiang
* @time 2018年12月15日下午9:00:19
*/
public class Demo2 {
public static ThreadLocal<String> t1 = new ThreadLocal<String>();
private static final Logger LOGGER = LoggerFactory.getLogger(Demo2.class); public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
if (t1.get() == null) {
LOGGER.info("从未放过值,threadName->{}", Thread.currentThread().getName());
t1.set("存放的值" + Thread.currentThread().getName());
}
LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
}
}, "thread1").start(); new Thread(new Runnable() {
@Override
public void run() {
if (t1.get() == null) {
LOGGER.info("从未放过值,threadName->{}", Thread.currentThread().getName());
t1.set("存放的值" + Thread.currentThread().getName());
}
LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
}
}, "thread2").start();
}
}
结果: (由结果可以看出每个线程存放了不同的值,但是在获取值的时候,每个线程又获取到了不同的值。)
21:11:08 [cn.qlq.thread.ten.Demo2]-[INFO] 从未放过值,threadName->thread2
21:11:08 [cn.qlq.thread.ten.Demo2]-[INFO] 从未放过值,threadName->thread1
21:11:08 [cn.qlq.thread.ten.Demo2]-[INFO] threadName - >thread2,值->存放的值thread2
21:11:08 [cn.qlq.thread.ten.Demo2]-[INFO] threadName - >thread1,值->存放的值thread1
3.解决get()返回null问题
为了解决返回为null的问题,也就是在get()的时候直接就返回默认值,采用继承ThreadLocal并且重写initialValue的方式实现。(初始值的时候也可以实现线程的隔离线)
package cn.qlq.thread.ten; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* 解决get()返回null的问题
*
* @author QiaoLiQiang
* @time 2018年12月15日下午9:16:17
*/
public class Demo3<T> extends ThreadLocal<String> {
public static Demo3<String> t1 = new Demo3<String>();
private static final Logger LOGGER = LoggerFactory.getLogger(Demo3.class); public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
if (t1.get() == null) {
LOGGER.info("从未放过值,threadName->{}", Thread.currentThread().getName());
t1.set("存放的值" + Thread.currentThread().getName());
}
LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
}
}, "thread1").start(); new Thread(new Runnable() {
@Override
public void run() {
if (t1.get() == null) {
LOGGER.info("从未放过值,threadName->{}", Thread.currentThread().getName());
t1.set("存放的值" + Thread.currentThread().getName());
}
LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
}
}, "thread2").start();
} @Override
protected String initialValue() {
return "这是初始值" + Thread.currentThread().getName();
}
}
结果:
21:23:54 [cn.qlq.thread.ten.Demo3]-[INFO] threadName - >thread2,值->这是初始值thread2
21:23:54 [cn.qlq.thread.ten.Demo3]-[INFO] threadName - >thread1,值->这是初始值thread1
4. ThreadLocal中存入多个对象
有时候我们在ThreadLocal 中希望共享多个变量。
最简单的一种办法创建一个ThreadLocal就是将所有共享的数据存入一个Map,将Map存入ThreadLocal,另一种办法就是所有共享数据放入一个bean中将bean存入ThreadLocal。
另一种办法就是每个创建多个ThreadLocal分别存放多种共享的数据。
如下一个ThreadLocal存入Map中实现共享多个数据
package cn.qlq.thread.ten; import java.util.HashMap;
import java.util.Map; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; @SuppressWarnings("all")
public class Demo7 {
public static ThreadLocal t1 = new ThreadLocal(); private static final Logger LOGGER = LoggerFactory.getLogger(Demo7.class); public static void main(String[] args) throws InterruptedException {
Map data = new HashMap();
data.put("str", "111222");
data.put("int", 11122);
data.put("obj", new Object());
t1.set(data);
Object object = t1.get();
System.out.println(object);
}
}
结果:
{str=111222, int=11122, obj=java.lang.Object@77700f3d}
或者多个ThreadLocal共享多个数据
package cn.qlq.thread.ten; import java.util.HashMap;
import java.util.Map; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; @SuppressWarnings("all")
public class Demo7 {
public static ThreadLocal t1 = new ThreadLocal();
public static ThreadLocal<String> t2 = new ThreadLocal<String>(); private static final Logger LOGGER = LoggerFactory.getLogger(Demo7.class); public static void main(String[] args) throws InterruptedException {
Map data = new HashMap();
data.put("str", "111222");
data.put("int", 11122);
data.put("obj", new Object());
t1.set(data); t2.set("t2"); System.out.println(t1.get());
System.out.println(t2.get());
}
}
结果:
{str=111222, int=11122, obj=java.lang.Object@271455a2}
t2
5. 类InheritableThreadLocal的使用
5.1值继承
使用InheritableThreadLocal可以在子线程从父线程中继承值。主线程存入值,在子线程中获取。
package cn.qlq.thread.ten; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* 主线程中设置值,子线程中获取值
*
* @author QiaoLiQiang
* @time 2018年12月15日下午9:29:40
* @param <T>
*/
public class Demo4<T> extends InheritableThreadLocal<String> {
public static Demo4<String> t1 = new Demo4<String>();
private static final Logger LOGGER = LoggerFactory.getLogger(Demo4.class); public static void main(String[] args) {
// 主线程中存入值
t1.set("存放的值" + Thread.currentThread().getName()); new Thread(new Runnable() {
@Override
public void run() {
if (t1.get() == null) {
LOGGER.info("从未放过值,threadName->{}", Thread.currentThread().getName());
t1.set("存放的值" + Thread.currentThread().getName());
}
LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
}
}, "thread1").start(); new Thread(new Runnable() {
@Override
public void run() {
if (t1.get() == null) {
LOGGER.info("从未放过值,threadName->{}", Thread.currentThread().getName());
t1.set("存放的值" + Thread.currentThread().getName());
}
LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
}
}, "thread2").start();
}
}
结果:
21:29:02 [cn.qlq.thread.ten.Demo4]-[INFO] threadName - >thread2,值->存放的值main
21:29:02 [cn.qlq.thread.ten.Demo4]-[INFO] threadName - >thread1,值->存放的值main
测试在子线程中再次创建子线程。(值会一直继承下去,对自己的子线程创建的子线程也有效)
package cn.qlq.thread.ten; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* 主线程中设置值,子线程中获取值
*
* @author QiaoLiQiang
* @time 2018年12月15日下午9:29:40
* @param <T>
*/
public class Demo5<T> extends InheritableThreadLocal<String> {
public static Demo5<String> t1 = new Demo5<String>();
private static final Logger LOGGER = LoggerFactory.getLogger(Demo5.class); public static void main(String[] args) {
// 主线程中存入值
t1.set("存放的值" + Thread.currentThread().getName()); // 创建子线程获取值
new Thread(new Runnable() {
@Override
public void run() {
LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get()); // 创建子子线程获取值
new Thread(new Runnable() {
@Override
public void run() {
LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
}
}, "thread2").start();
}
}, "thread1").start();
}
}
结果:
21:32:34 [cn.qlq.thread.ten.Demo5]-[INFO] threadName - >thread1,值->存放的值main
21:32:34 [cn.qlq.thread.ten.Demo5]-[INFO] threadName - >thread2,值->存放的值main
5.2 值继承再修改
值也可以被继承再修改。
package cn.qlq.thread.ten; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* 继承再修改值
*
* @author QiaoLiQiang
* @time 2018年12月15日下午9:34:41
* @param <T>
*/
public class Demo6<T> extends InheritableThreadLocal<String> {
public static Demo6<String> t1 = new Demo6<String>();
private static final Logger LOGGER = LoggerFactory.getLogger(Demo6.class); public static void main(String[] args) throws InterruptedException {
// 主线程中存入值
t1.set("存放的值" + Thread.currentThread().getName());
LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get()); // 创建子线程获取值
new Thread(new Runnable() {
@Override
public void run() {
LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get()); // 主线程中存入值
t1.set("存放的值" + Thread.currentThread().getName());
LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get()); // 创建子子线程获取值
new Thread(new Runnable() {
@Override
public void run() {
LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
}
}, "thread2").start();
}
}, "thread1").start(); Thread.sleep(2 * 1000);
LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
}
}
结果:(主线程中存入值,在子线程修改了值,main线程取到的值还是main中存入的值,子线程以及子子线程获得的值是子线程修改的值。)
21:36:26 [cn.qlq.thread.ten.Demo6]-[INFO] threadName - >main,值->存放的值main
21:36:26 [cn.qlq.thread.ten.Demo6]-[INFO] threadName - >thread1,值->存放的值main
21:36:26 [cn.qlq.thread.ten.Demo6]-[INFO] threadName - >thread1,值->存放的值thread1
21:36:26 [cn.qlq.thread.ten.Demo6]-[INFO] threadName - >thread2,值->存放的值thread1
21:36:28 [cn.qlq.thread.ten.Demo6]-[INFO] threadName - >main,值->存放的值main
6.ThreadLocal 源码解析
ThreadLocal其实比较简单,因为类里就三个public方法:set(T value)、get()、remove()。
三个理论基础:
1、每个线程都有一个自己的ThreadLocal.ThreadLocalMap对象 (ThreadLocalMap 是ThreadLocal的静态内部类,一个类似于Map结构的普通类,没有实现Map接口,也是内部维护一个静态内部类Entry存放数据,而且其内部的Entry继承 WeakReference弱引用(被若引用关联的对象只能生存到下一次垃圾回收之前。其内部的key是Threadlocal,value就是存入的值)。)
Thread.class中的一个成员属性:
ThreadLocal.ThreadLocalMap threadLocals = null;
2、每一个ThreadLocal对象都有一个循环计数器
3、ThreadLocal.get()取值,就是根据当前的线程,获取线程中自己的ThreadLocal.ThreadLocalMap,然后在这个Map中根据第二点中循环计数器取得一个特定value值
6.1 set(T value)源码解读
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 ,如果 获取到的ThreadLocalMap不为空的话就直接set值,否则走 createMap方法创建map。
(1)getmap(t)从线程中获取 ThreadLocalMap (上面说过了每个Thread都有都有一个自己的ThreadLocal.ThreadLocalMap对象)
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
(2)map.set(this,value)设置值
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal key, Object value) { // We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not. Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get(); if (k == key) {
e.value = value;
return;
} if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
} tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
/**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
private final int threadLocalHashCode = nextHashCode(); /**
* The next hash code to be given out. Updated atomically. Starts at
* zero.
*/
private static AtomicInteger nextHashCode =
new AtomicInteger();
这个Map存储的方式不是链表法而是开地址法。看到设置table中的位置的时候,都把一个static的nextHashCode累加一下,这意味着,set的同一个value,可能在每个ThreadLocal.ThreadLocalMap中的table中的位置都不一样。
- 先对ThreadLocal里面的threadLocalHashCode取模获取到一个table中的位置
- 这个位置上如果有数据,获取这个位置上的ThreadLocal
(1)判断一下位置上的ThreadLocal和我本身这个ThreadLocal是不是一个ThreadLocal,是的话数据就覆盖,返回
(2)不是同一个ThreadLocal,再判断一下位置上的ThreadLocal是是不是空的,这个解释一下。Entry是ThreadLocalMap的一个静态内部类,并且是弱引用,"static class Entry extends WeakReference<ThreadLocal>",有可能这个 Entry 被垃圾回收了,这时候把新设置的value替换到当前位置上,返回
(3)上面都没有返回,给模加1,看看模加1后的table位置上是不是空的,是空的再加1,判断位置上是不是空的...一直到找到一个table上的位置不是空的为止,往这里面塞一个value。换句话说,当table的位置上有数据的时候,ThreadLocal采取的是办法是找最近的一个空的位置设置数据。
3.这个位置上如果没有数据,就创建一个Entry到这个位置。
(3) createMap(thread,value)方法查看:
void createMap(Thread t, T firstValue) {
t.threadLocals = 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);
}
创建一个ThreadLocalMap并且将引用传递给线程对象的 threadLocals 。
ThreadLocalMap创建的时候做了一些初始化工作,并且将值设置进去。
6.2 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)
return (T)e.value;
}
return setInitialValue();
}
获取到当前线程-》获取到当前线程里面的ThreadLocalMap -》如果ThreadLocalMap 为不为null,获取其内部的Entry对象-》获取entry的value(根据ThreadLocal 获取一个下标,然后获取对应下标的entry的信息)
private Entry getEntry(ThreadLocal key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
如果ThreadLocalMap 为null,做默认设置并且返回默认值(这也是我们在上面的例子中继承ThreadLocal重写initialValue方法可以设置默认值的原因)
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;
}
6.3 remove()源码解读
remove()源码如下:
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
private void remove(ThreadLocal key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
根据当前线程获取到ThreadLocalMap-》如果获取的ThreadLocalMap不为null,调用其remove(key)方法
remove(ThreadLocal)根据ThreadLocal 对象获取一个下标i,如果tab[i]不为null,即存在对应的entry,调用entry的clear方法
补充:clear方法是Reference类型一个方法:--其作用就是将referent置为null,垃圾回收就可以回收此对象。
public void clear() {
this.referent = null;
}
类ThreadLocal的使用与源码分析的更多相关文章
- 并发编程学习笔记(8)----ThreadLocal的使用及源码分析
1. ThreadLocal的理解 ThreadLocal,顾名思义,就是线程的本地变量,ThreadLocal会为每个线程创建一个本地变量副本,使得使用ThreadLocal管理的变量在多线程的环境 ...
- ThreadLocal应用场景以及源码分析
一.应用篇 ThreadLocal介绍 ThreadLocal如果单纯从字面上理解的话好像是“本地线程”的意思,其实并不是这个意思,只是这个名字起的太容易让人误解了,它的真正的意思是线程本地变量. 实 ...
- Cocos2d-X3.0 刨根问底(五)----- Node类及显示对象列表源码分析
上一章 我们分析了Cocos2d-x的内存管理,主要解剖了 Ref.PoolManager.AutoreleasePool这三个类,了解了对象是如何自动释放的机制.之前有一个类 Node经常出现在各种 ...
- Java中集合框架,Collection接口、Set接口、List接口、Map接口,已经常用的它们的实现类,简单的JDK源码分析底层实现
(一)集合框架: Java语言的设计者对常用的数据结构和算法做了一些规范(接口)和实现(实现接口的类).所有抽象出来的数据结构和操作(算法)统称为集合框架. 程序员在具体应用的时候,不必考虑数据结构和 ...
- spring启动component-scan类扫描加载过程---源码分析
http://blog.csdn.net/xieyuooo/article/details/9089441#comments
- ThreadLocal定义、使用案例及源码分析
原文连接:(http://www.studyshare.cn/blog/details/1165/0 ) 一.ThreadLocal定义 jdk官方文档定义是:该类提供线程局部变量. 这些变量与其正常 ...
- Java IO 之 FileInputStream & FileOutputStream源码分析
Writer :BYSocket(泥沙砖瓦浆木匠) 微 博:BYSocket 豆 瓣:BYSocket FaceBook:BYSocket Twitter ...
- Springboot中注解@Configuration源码分析
Springboot中注解@Configuration和@Component的区别 1.先说结论,@Configuration注解上面有@Component注解,所以@Component有的功能@Co ...
- ABP源码分析十四:Entity的设计
IEntity<TPrimaryKey>: 封装了PrimaryKey:Id,这是一个泛型类型 IEntity: 封装了PrimaryKey:Id,这是一个int类型 Entity< ...
随机推荐
- day16-(listener&filter)
回顾: ajax: 异步请求 原生的ajax(了解) 1.创建一个核心对象 XMLHttpRequest 2.编写回调函数 xmlhttp.onreadystatechange=function(){ ...
- puppeteer,新款headless chrome
puppeteer puppeteer是一种谷歌开发的Headless Chrome,因为puppeteer的出现,业内许多自动化测试库停止维护,比如PhantomJS,Selenium IDE fo ...
- JAVA核心技术I---JAVA基础知识(格式化相关类)
一:格式化相关类 (一)java.text包java.text.Format的子类 –NumberFormat:数字格式化,抽象类 DecimalFormat –MessageFormat:字符串格式 ...
- testlink for windows 安装
testlink的使用说明可到官网查看:http://www.testlink.org.cn/509.html 一.安装xampp 到xampp官网中下载安装文件,按步骤安装即可. 二.Testlin ...
- Kafka技术内幕 读书笔记之(二) 生产者——新生产者客户端
消息系统通常由生产者(producer ). 消费者( consumer )和消息代理( broker ) 三大部分组成,生产者会将消息写入消息代理,消费者会从消息代理中读取消息 . 对于消息代理而言 ...
- 免费开源.net的pdf操作控件PdfiumViewer
最终我找到了pdffiumViewer.开源免费的.net组件. 亲测,可以按第一个下载地址,改写开发.如果对源码感兴趣,可以上GitHub网站 效果图: 1.源代码下载地址: https://do ...
- js静态方法与实例方法定义,js回调方法定义
主要为了回调方法,随便把静态言法和实例方法也回顾一下. <script type="text/javascript"> var fun = { //下面是静态方法(第一 ...
- 【1】【leetcode-130】 被围绕的区域
(DFS思路对,写复杂了) 给定一个二维的矩阵,包含 'X' 和 'O'(字母 O). 找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充. 示例: X X X X X O ...
- Mysql查看表的建表语句
已查询Test的建表语句为例: SHOW CREATE TABLE TEST
- c++后台开发路线