java之ThreadLocal详解
一、ThreadLocal简介
ThreadLocal是线程的局部变量,是每一个线程所单独持有的,其他线程不能对其进行访问,通常是类中的private static字段。
我们知道有时候一个对象的变量会被多个线程所访问,这时就会有线程安全问题,当然我们可以使用synchorinized 关键字来为此变量加锁,进行同步处理,从而限制只能有一个线程来使用此变量,但是加锁会大大影响程序执行效率,此外我们还可以使用ThreadLocal来解决对某一个变量的访问冲突问题。
当使用ThreadLocal维护变量的时候 为每一个使用该变量的线程提供一个独立的变量副本,即每个线程内部都会有一个该变量,这样同时多个线程访问该变量并不会彼此相互影响,因此他们使用的都是自己从内存中拷贝过来的变量的副本, 这样就不存在线程安全问题,也不会影响程序的执行性能。
但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。
二、ThreadLocal源码分析
(1)ThreadLocal方法
ThreadLocal 的几个方法: ThreadLocal 可以存储任何类型的变量对象, get返回的是一个Object对象,但是我们可以通过泛型来制定存储对象的类型。
public T get() { } // 用来获取ThreadLocal在当前线程中保存的变量副本
public void set(T value) { } //set()用来设置当前线程中变量的副本
public void remove() { } //remove()用来移除当前线程中变量的副本
protected T initialValue() { } //initialValue()是一个protected方法,一般是用来在使用时进行重写的
(2)ThreadLocal实现原理
1.内部结构
Thread 在内部是通过ThreadLocalMap来维护ThreadLocal变量表, 在Thread类中有一个threadLocals 变量,是ThreadLocalMap类型的,它就是为每一个线程来存储自身的ThreadLocal变量的。
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap 是定义在ThreadLocal 类里的内部类,它的作用是存储线程的局部变量。ThreadLocalMap 以ThreadLocal的引用作为键,以局部变量作为值,存储在ThreadLocalMap.Entry (一种存储键值的数据结构)里。这是因为在每一个线程里面,可能存在着多个ThreadLocal变量
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
2.调用过程
初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找
注意:
在进行get之前,必须先set,否则会报空指针异常;
如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。
ThreadLocal的set(T value)方法
public void set(T value) {
// 获得当前线程
Thread t = Thread.currentThread();
// 获得当前线程的 ThreadLocalMap 引用,详细见下
ThreadLocalMap map = getMap(t);
// 如果不为空,则更新局部变量的值
if (map != null)
map.set(this, value);
//如果不是第一次使用,先进行初始化
else
createMap(t, value);
}
内部类ThreadLocalMap的set(ThreadLocal<?> key,Object value)
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// Hash 寻址,与table数组长度减1(二进制全是1)相与,所以数组长度必须为2的次方,减小hash重复的可能性
int i = key.threadLocalHashCode & (len-1);
//从hash值计算出的下标开始遍历
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//获得该Entry的键
ThreadLocal<?> k = e.get();
//如果键和传过来的相同,覆盖原值,也说明,一个ThreadLocal变量只能为一个线程保存一个局部变量
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();
}
可以看出ThreadLocalMap 采用线性探测再散列解决Hash冲突的问题。即,如果一次Hash计算出来的数组下标被占用,即hash值重复了,则在该下标的基础上加1测试下一个下标,直到找到空值。比如说,Hash计算出来下标i为6,table[6] 已经有值了,那么就尝试table[7]是否被占用,依次类推,直到找到空值。以上,就是保存线程本地变量的方法。
TheadLocal的get()方法
public T get() {
//获得当前线程
Thread t = Thread.currentThread();
//得到当前线程的一个threadLocals 变量
ThreadLocalMap map = getMap(t);
if (map != null) {
// 如果不为空,以当前ThreadLocal为主键获得对应的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//如果值为空,则进行初始化
return setInitialValue();
}
ThreadLocal的setInitialValue()方法
private T setInitialValue() {
//获得初始默认值
T value = initialValue();
//得到当前线程
Thread t = Thread.currentThread();
// 获得该线程的ThreadLocalMap引用
ThreadLocalMap map = getMap(t);
//不为空则覆盖
if (map != null)
map.set(this, value);
else
//若是为空,则进行初始化,键为本ThreadLocal变量,值为默认值
createMap(t, value);
}
// 默认初始化返回null值,这也是 下面demo 为什么需要重写该方法的原因。如果没有重写,第一次get()操作获得的线程本地变量为null,需要进行判断并手动调用set()进行初始化
protected T initialValue() {
return null;
}
三、Demo
下面是个ThreadLocal使用的实例,两个任务共享同一个变量,并且两个任务都把该变量设置为了线程私有变量,这样,虽然两个任务都”持有“同一变量,但各自持有该变量的拷贝。因此,当一个线程修改该变量时,不会影响另一线程该变量的值。
package thread.ThreadLocalTest;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* Created by StoneGeek on 2018/8/1.
* 博客地址:http://www.cnblogs.com/sxkgeek
*/
public class ThreadLocalDemo2 implements Runnable {
// 一般会把 ThreadLocal 设置为static 。它只是个为线程设置局部变量的入口,多个线程只需要一个入口
private static ThreadLocal<Student> localStudent = new ThreadLocal() {
// 一般会重写初始化方法,一会分析源码时候会解释为什么
@Override
public Student initialValue() {
return new Student();
}
};
private Student student = null;
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println("【" + threadName + "】:is running !");
Random ramdom = new Random();
//随机生成一个变量
int age = ramdom.nextInt(100);
System.out.println("【" + threadName + "】:set age to :" + age);
// 获得线程局部变量,改变属性值
Student stu = getStudent();
stu.setAge(age);
System.out.println("【" + threadName + "】:第一次读到的age值为 :" + stu.getAge());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("【" + threadName + "】:第二次读到的age值为 :" + stu.getAge());
}
public Student getStudent() {
student = localStudent.get();
// 如果不重写初始化方法,则需要判断是否为空,然后手动为ThreadLocal赋值,否则的话会报空指针异常
// if(student == null){
// student = new Student();
// localStudent.set(student);
// }
return student;
}
public static void main(String[] args) {
ThreadLocalDemo2 ll = new ThreadLocalDemo2();
Thread t1 = new Thread(ll, "线程1");
Thread t2 = new Thread(ll, "线程2");
t1.start();
t2.start();
}
}
console打印:
【线程2】:is running !
【线程1】:is running !
【线程1】:set age to :67
【线程2】:set age to :4
【线程1】:第一次读到的age值为 :67
【线程2】:第一次读到的age值为 :4
【线程1】:第二次读到的age值为 :67
【线程2】:第二次读到的age值为 :4
四、应用场景
最常见的ThreadLocal使用场景为
用来解决 数据库连接、Session管理等。
数据库连接
Class A implements Runnable{
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
}
Session管理
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
java之ThreadLocal详解的更多相关文章
- 最强Java并发编程详解:知识点梳理,BAT面试题等
本文原创更多内容可以参考: Java 全栈知识体系.如需转载请说明原处. 知识体系系统性梳理 Java 并发之基础 A. Java进阶 - Java 并发之基础:首先全局的了解并发的知识体系,同时了解 ...
- Java 字符串格式化详解
Java 字符串格式化详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 文中如有纰漏,欢迎大家留言指出. 在 Java 的 String 类中,可以使用 format() 方法 ...
- Java 序列化Serializable详解
Java 序列化Serializable详解(附详细例子) Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化Serialization(序列化)是一种将对象以一连 ...
- Java String类详解
Java String类详解 Java字符串类(java.lang.String)是Java中使用最多的类,也是最为特殊的一个类,很多时候,我们对它既熟悉又陌生. 类结构: public final ...
- 最新java数组的详解
java中HashMap详解 http://alex09.iteye.com/blog/539545 总结: 1.就像引用类型的数组一样,当我们把 Java 对象放入数组之时,并不是真正的把 Java ...
- JAVA IO 类库详解
JAVA IO类库详解 一.InputStream类 1.表示字节输入流的所有类的超类,是一个抽象类. 2.类的方法 方法 参数 功能详述 InputStream 构造方法 available 如果用 ...
- 转:Java HashMap实现详解
Java HashMap实现详解 转:http://beyond99.blog.51cto.com/1469451/429789 1. HashMap概述: HashMap是基于哈希表的M ...
- 淘宝JAVA中间件Diamond详解(2)-原理介绍
淘宝JAVA中间件Diamond详解(二)---原理介绍 大家好,通过第一篇的快速使用,大家已经对diamond有了一个基本的了解.本次为大家带来的是diamond核心原理的介绍,主要包括server ...
- 【转】 java中HashMap详解
原文网址:http://blog.csdn.net/caihaijiang/article/details/6280251 java中HashMap详解 HashMap 和 HashSet 是 Jav ...
随机推荐
- 新建项目中的fail和missing之类的问题
首先必须要明白:每一个项目需要都要安装在本地仓库中去--->pom,war,jar无一例外 如果不安装会出现下面的错误: 解决的办法就是: 先clean后install项目,把他们安装到本地仓库 ...
- Python数据库小程序
源代码: # dict1 是 字典 , 用来对应相应元素的下标,我们将文件转成列表,对应的也就是文件的下标,通过下标来找文件元素 dict1 = {'sort':0 , 'name':1 ,'age' ...
- <xsl:apply-templates>和<xsl:call-template>的区别
<xsl:apply-templates> 应用模板,故名思意,将定义好的模板应用到 XML 的节点上. 可以调用 XML 文档的节点,使 XSL 文档可以渲染 XML 元素内的数据, ...
- jar包运行出现中文乱码的问题
写了一个java Project,在eclipse里运行的时候不会出现乱码,但是用jar包运行的时候,出现了中文乱码. 我将它包装成了一个文件,这样就在没有jre的机器上也可以运行了.start.ba ...
- Winform中DevExpress的TreeList的入门使用教程(附源码下载)
场景 Winform控件-DevExpress18下载安装注册以及在VS中使用: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/1 ...
- react navigation goBack()返回到任意页面(不集成redux) 二
实现思路: A -- > B (获取A的key值,传至C)-- >C(获取B传来的A页面key值,传至D) -- >D(获取C传来的A页面key值&C页面的key值,传至下一 ...
- Disruptor原理探讨
之前谈到了在我的项目里用到了Disruptor,因为对它了解不足的原因,才会引发之前的问题,因此,今天特意来探讨其原理. 为什么采用Disruptor 先介绍一下我的这个服务.这个服务主要是作为游戏服 ...
- MYSQL之概念基础篇
1数据库概述 1.1 数据管理技术的产生和发展 数据库技术是应数据库管理任务的需要而产生的.20世纪50年代中期以前,计算机主要是用于科学计算.当时的硬件状况是,外存只有纸带.卡片.磁带,没有磁盘等可 ...
- Day 18 软件管理3之搭建网络仓库
搭建一个网络仓库 服务端: 10.0.0.200 1.准备软件包( 1.光盘 2.缓存 3.联网下载 4.同步 ) 2.通过p共享软件包存放的目录 3.将光盘中的软件包都拷贝至p的共享目录下 4. ...
- 实现一个正则表达式引擎in Python(一)
前言 项目地址:Regex in Python 开学摸鱼了几个礼拜,最近几天用Python造了一个正则表达式引擎的轮子,在这里记录分享一下. 实现目标 实现了所有基本语法 st = 'AS342abc ...