前面也听说了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线程设置私有变量底层源码分析的更多相关文章

  1. Vector总结及部分底层源码分析

    Vector总结及部分底层源码分析 1. Vector继承的抽象类和实现的接口 Vector类实现的接口 List接口:里面定义了List集合的基本接口,Vector进行了实现 RandomAcces ...

  2. java 日志体系(四)log4j 源码分析

    java 日志体系(四)log4j 源码分析 logback.log4j2.jul 都是在 log4j 的基础上扩展的,其实现的逻辑都差不多,下面以 log4j 为例剖析一下日志框架的基本组件. 一. ...

  3. java中的==、equals()、hashCode()源码分析(转载)

    在java编程或者面试中经常会遇到 == .equals()的比较.自己看了看源码,结合实际的编程总结一下. 1. ==  java中的==是比较两个对象在JVM中的地址.比较好理解.看下面的代码: ...

  4. Java的三种代理模式&完整源码分析

    Java的三种代理模式&完整源码分析 参考资料: 博客园-Java的三种代理模式 简书-JDK动态代理-超详细源码分析 [博客园-WeakCache缓存的实现机制](https://www.c ...

  5. LInkedList总结及部分底层源码分析

    LInkedList总结及部分底层源码分析 1. LinkedList的实现与继承关系 继承:AbstractSequentialList 抽象类 实现:List 接口 实现:Deque 接口 实现: ...

  6. Java SPI机制实战详解及源码分析

    背景介绍 提起SPI机制,可能很多人不太熟悉,它是由JDK直接提供的,全称为:Service Provider Interface.而在平时的使用过程中也很少遇到,但如果你阅读一些框架的源码时,会发现 ...

  7. List-LinkedList、set集合基础增强底层源码分析

    List-LinkedList 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 继上一章继续讲解,上章内容: List-ArreyLlist集合基础增强底层源码分析:https:// ...

  8. List-ArrayList集合基础增强底层源码分析

    List集合基础增强底层源码分析 作者:Stanley 罗昊 [转载请注明出处和署名,谢谢!] 集合分为三个系列,分别为:List.set.map List系列 特点:元素有序可重复 有序指的是元素的 ...

  9. JAVA ArrayList集合底层源码分析

    目录 ArrayList集合 一.ArrayList的注意事项 二. ArrayList 的底层操作机制源码分析(重点,难点.) 1.JDK8.0 2.JDK11.0 ArrayList集合 一.Ar ...

随机推荐

  1. NestedInteger Java

    ''' class NestedInteger { private List list; private Integer integer; public NestedInteger(List<N ...

  2. RabbitMQ的六种工作模式总结

    最近学习RabbitMQ的使用方式,记录下来,方便以后使用,也方便和大家共享,相互交流. RabbitMQ的六种工作模式: 1.Work queues2.Publish/subscribe3.Rout ...

  3. (转)在阿里云 CentOS 服务器(ECS)上搭建 nginx + mysql + php-fpm 环境

    阿里云的云服务器(ECS)可以选择多种操作系统,打算用它运行 Drupal或者 WordPress ,你最好选择 Linux 系统,这篇文章的演示是基于阿里云的 CentOS 操作系统的服务器.我们在 ...

  4. loadrunner12下载、安装、认证、汉化

    友情提示 推荐工具pandownload下载 本文尽可能地写得详细一些,有不懂的请先自行百度 安装过程中会有大量英文,可以用有道词典截图翻译 若你的电脑只有一个分区,则建议所有位置选择默认,或者根据个 ...

  5. mybatis简单入门介绍

    mybatis入门 简介 什么是mybatis? MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及 ...

  6. 通过父级id获取到其下所有子级(无穷级)——Mysql函数实现

    [需求]某用户只能查看其自己信息及其下级信息,涉及通过该用户所在部门获取其下所有部门(多层)id集合. 步骤一:对数据库进行设置: set global log_bin_trust_function_ ...

  7. 玩转 SpringBoot 2 之整合 JWT 下篇

    前言 在<玩转 SpringBoot 2 之整合 JWT 上篇> 中介绍了关于 JWT 相关概念和JWT 基本使用的操作方式.本文为 SpringBoot 整合 JWT 的下篇,通过解决 ...

  8. Scala和Java混合项目搭建:(Eclipse)

     Scala和Java混合项目搭建:(Eclipse)  项目结构: pom.xml: <project xmlns="http://maven.apache.org/POM/4.0. ...

  9. IDEA 学习笔记之 Web项目开发

    Web项目开发: 添加新模块: 起名: 添加jars: 添加Tomcat/local: 添加项目:  启动Tomcat: 看到web页面: 修改页面: 重新部署页面:

  10. php导出excel乱码怎么处理

    使用PHP导出excel文档,有时候莫名其妙就会出现导出的数据乱码,现在推荐一个万能修补大法 话不多说,直接上代码 核心就是在处理完数据之后,输出excel文件之前 添加 ob_end_clean() ...