java ThreadLocal线程设置私有变量底层源码分析
前面也听说了ThreadLocal来实现高并发,以前都是用锁来实现,看了挺多资料的,发现其实还是区别挺大的(感觉严格来说ThreadLocal并不算高并发的解决方案),现在总结一下吧。
高并发中会出现的问题就是线程安全问题,可以说是多个线程对共享资源访问如何处理的问题,处理不当会的话,会出现结果和预期会完全不同。
一般情况下,多个线程访问一个变量都是公用他们的值,不过有时候虽然也是访问共享变量,不过每个线程却需要自己的私有变量。这个时候ThreadLocal就有用武之地了。下面是个ThreadLocal的简单实例:
public class ThreadLocalExample {
public static void main(String[] args){
//创建一个ThreadLocal对象
ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); //设置主线程私有变量值
threadLocal.set(100); //创建一个新线程
new Thread(new Runnable(){
public void run(){
//使用共享变量,设置线程私有变量
threadLocal.set(50);
System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
}
}).start(); System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
}
}
输出结果:
main:100
Thread-0:50
很神奇,对多个资源之间的共享,又不想他们之间相互影响,所以使用这个是挺不错的。具体应用,spring中应用我记得挺多的,连接数据库的每个连接,还有session的存储。
思考了一下,要我实现的话就用个map来存储,因为这个其实就是键值对,只不过键是线程唯一标识,值就是对应的私有变量。
具体看了源码发现差不多,不过使用内部自己实现的一个ThreadLocalMap类,内部还一个Entry类而且Entry类继承weakRefrence(说实话第一次遇到弱应用,以前只是在jvm那本书学习了下),具体方法如下:
先看下他的set方法吧
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);
}
getMap方法的话一个获得所有线程共享的ThreadLocalMap对象如下:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
然后进入Thread类进去找一下这个容器,找到下面:
ThreadLocal.ThreadLocalMap threadLocals = null;
然后创建:
void createMap(Thread t, T firstValue) {
//创建ThreadLocalMap对象赋给threadLocals
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
至此,ThreadLocal的基本原理就已经很清晰了:各线程对共享的ThreadLocal实例进行操作,实际上是以该实例为键对内部持有的ThreadLocalMap对象进行操作。
还有get()方法的话就是利用设置的键进行获取,remove()方法也是,其实和Hashmap差不多不过解决冲突使用的拉链法(对了,下次写一篇HashHap的还有ConcurrentHashMap的话,颇有研究)。这里有个问题就是因为这个ThreadLocalMap是静态的所以在方法区中(jdk8之后为元数据区),不进行回收的话会造成内存泄漏,而且可能会出现内存溢出,所以使用后记得remove();
基本上其实可以了,不过好奇ThreadLocalMap怎么实现的可以接着往下看,我也好奇,所以也偷偷看了,嘿嘿嘿
那就来分析一下这个ThreadLocalMap这个内部类吧。
ThreadLocalMap属于一个自定义的map,是一个带有hash功能的静态内部类,其实和java.util包下提供的Map类并没有关系。内部有一个静态的Entry类,下面具体分析Entry。
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value; Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
偷了一下官方的解释:
主要是说entry继承自WeakReference,用main方法引用的字段作为entry中的key。当entry.get() == null的时候,意味着键将不再被引用。
注意看到一个super(k),说明调用父类的构造,去看看
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
就上面这个其他没了,看了半天有点没看懂,然后去学了四种引用回来终于看懂,由于篇幅过多,在结尾我给出两篇别人的博客,可以去看完了,再回来,多学点哈哈哈。
再看了下发现这个内部类好多,但是其实就是map的一种实现,上面也讲了set方法那就简单提一下ThreadLocalMap的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对象数组拿到来
ThreadLocal.ThreadLocalMap.Entry[] tab = table;
//长度也拿到来
int len = tab.length;
//通过拿到key的hashcode值,进去发现神奇的一幕这里利用通过累加这个值0x61c88647来作为hashcode,
// 这里提一下往下走发现因为要公用这个属性,多个实例访问会有问题
// 所以使用了AtomicInteger原子操作来写值
//并且与总长度-1做与运算就是取模,因为扩容都是2的n次方所以这样直接取模就行,速度快
int i = key.threadLocalHashCode & (len-1); //定位到对应的数组位置,进行冲突判断之类的处理
for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) { //这里是冲突遍历 //这里里面就拿对应tabel下对应位置的当前引用
ThreadLocal<?> k = e.get(); //判断是不是对应的键,是的话就覆盖
if (k == key) {
e.value = value;
return;
}
//没有的话就生成Entry代替掉
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//这里就直接插入了
tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value);
//长度加1
int sz = ++size;
//判断是否做扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
里面其实挺复杂的,具体的话就是正常是使用开放定址法处理,这里使用累加一个定值解决的冲突,因为多个实例共用,特殊处理,厉害厉害。
//threadLocalHashCode代码也贴在这里吧,有兴趣可以直接去看 private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
private final int threadLocalHashCode = nextHashCode();
总结
看完源码之后神清气爽,学到了很多啦。以前对java引用只是知道四个引用和对应的相应简单概念,为了看懂这个Entry,去学习了weakReference源码,看了别人的关于四个引用的博客写的真好,偷偷学习了下,并且知道怎么使用了。划重点会用了!!!当然对于ThreadLocal也会用了,而且好像可以手写一个简单的版本哎,可以动手试试。
关于四种引用博客,写的真的很棒。
https://blog.csdn.net/swebin/article/details/78571933
https://blog.csdn.net/hacker_zhidian/article/details/83043270
java ThreadLocal线程设置私有变量底层源码分析的更多相关文章
- Vector总结及部分底层源码分析
Vector总结及部分底层源码分析 1. Vector继承的抽象类和实现的接口 Vector类实现的接口 List接口:里面定义了List集合的基本接口,Vector进行了实现 RandomAcces ...
- java 日志体系(四)log4j 源码分析
java 日志体系(四)log4j 源码分析 logback.log4j2.jul 都是在 log4j 的基础上扩展的,其实现的逻辑都差不多,下面以 log4j 为例剖析一下日志框架的基本组件. 一. ...
- java中的==、equals()、hashCode()源码分析(转载)
在java编程或者面试中经常会遇到 == .equals()的比较.自己看了看源码,结合实际的编程总结一下. 1. == java中的==是比较两个对象在JVM中的地址.比较好理解.看下面的代码: ...
- Java的三种代理模式&完整源码分析
Java的三种代理模式&完整源码分析 参考资料: 博客园-Java的三种代理模式 简书-JDK动态代理-超详细源码分析 [博客园-WeakCache缓存的实现机制](https://www.c ...
- LInkedList总结及部分底层源码分析
LInkedList总结及部分底层源码分析 1. LinkedList的实现与继承关系 继承:AbstractSequentialList 抽象类 实现:List 接口 实现:Deque 接口 实现: ...
- Java SPI机制实战详解及源码分析
背景介绍 提起SPI机制,可能很多人不太熟悉,它是由JDK直接提供的,全称为:Service Provider Interface.而在平时的使用过程中也很少遇到,但如果你阅读一些框架的源码时,会发现 ...
- List-LinkedList、set集合基础增强底层源码分析
List-LinkedList 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 继上一章继续讲解,上章内容: List-ArreyLlist集合基础增强底层源码分析:https:// ...
- List-ArrayList集合基础增强底层源码分析
List集合基础增强底层源码分析 作者:Stanley 罗昊 [转载请注明出处和署名,谢谢!] 集合分为三个系列,分别为:List.set.map List系列 特点:元素有序可重复 有序指的是元素的 ...
- JAVA ArrayList集合底层源码分析
目录 ArrayList集合 一.ArrayList的注意事项 二. ArrayList 的底层操作机制源码分析(重点,难点.) 1.JDK8.0 2.JDK11.0 ArrayList集合 一.Ar ...
随机推荐
- Hadoop集群常用的shell命令
Hadoop集群常用的shell命令 Hadoop集群常用的shell命令 查看Hadoop版本 hadoop -version 启动HDFS start-dfs.sh 启动YARN start-ya ...
- 算法与数据结构基础 - 深度优先搜索(DFS)
DFS基础 深度优先搜索(Depth First Search)是一种搜索思路,相比广度优先搜索(BFS),DFS对每一个分枝路径深入到不能再深入为止,其应用于树/图的遍历.嵌套关系处理.回溯等,可以 ...
- Spring Boot 2.x基础教程:构建RESTful API与单元测试
首先,回顾并详细说明一下在快速入门中使用的@Controller.@RestController.@RequestMapping注解.如果您对Spring MVC不熟悉并且还没有尝试过快速入门案例,建 ...
- 死磕 java同步系列之mysql分布式锁
问题 (1)什么是分布式锁? (2)为什么需要分布式锁? (3)mysql如何实现分布式锁? (4)mysql分布式锁的优点和缺点? 简介 随着并发量的不断增加,单机的服务迟早要向多节点或者微服务进化 ...
- C语言入门-字符串
还是要继续学习,每天给自己打气. 字符数组 char word[] = {'H','e','l','l','o'}; 这不是c语言的字符串,不能用字符串的方式做计算 一.字符串 char word[] ...
- HDU 2044——一只小蜜蜂...(DP)
链接:http://acm.hdu.edu.cn/showproblem.php?pid=2044 题解 //递归思想,超时 #include<iostream> using namesp ...
- 使用 chart 部署 skywalking
使用 chart 部署 skywalking 本文主要讲述的是如何使用 Helm Charts 将 SkyWalking 部署到 Kubernetes 集群中,相关文档可以参考skywalking-k ...
- Jmeter定时器:Precise Throughput Timer使用
百度上这方面资料很少,google上有一些,我试着总结一下. Precise Throughput Timer 是Jmeter4.0添加的新功能,直接摘录一段英文: The Precise Throu ...
- 超大规模商用 K8s 场景下,阿里巴巴如何动态解决容器资源的按需分配问题?
作者 | 张晓宇(衷源) 阿里云容器平台技术专家 关注『阿里巴巴云原生』公众号,回复关键词"1010",可获取本文 PPT. 导读:资源利用率一直是很多平台管理和研发人员关心的话 ...
- C++set 和 multiset的使用
最后一个自由支配的暑假,学一些自己感兴趣的部分,也算为大三作准备. C++中set集合的使用 定义一个int类型的集合 set<int> s: set<int>::iterat ...