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

原文地址:https://www.cnblogs.com/calaMan/p/11753801.html

时间: 2024-10-07 08:18:25

java ThreadLocal线程设置私有变量底层源码分析的相关文章

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

在java编程或者面试中经常会遇到 == .equals()的比较.自己看了看源码,结合实际的编程总结一下. 1. ==  java中的==是比较两个对象在JVM中的地址.比较好理解.看下面的代码: 1 public class ComAddr{ 2 public static void main(String[] args) throws Exception { 3 String s1 = "nihao"; 4 String s2 = "nihao"; 5 Str

【小白的java成长系列】——顶级类Object源码分析

首先来说一下api文档使用,api这个词对有一定开发经验的java编程人员来说是很喜爱的~ java当然也提供了api开发文档,下载地址:http://www.oracle.com/technetwork/java/javase/downloads/index.html 找到下面的: 下载自己喜爱的版本即可,解压,点击~/jdk-7u60-apidocs/api/index.html就可以查看其api了: 跟上网一样一样的,点击相应链接就可以查看其信息了. 进入正题,说说Object这个类: 先

List集合基础增强底层源码分析

List集合基础增强底层源码分析 作者:Stanley 罗昊 集合分为三个系列,分别为:List.set.map List系列 特点:元素有序可重复 有序指的是元素的添加顺序,也就是说,元素被第一个存进去的时候,它就在第一位,这就是list集合的元素添加顺序: 通常情况下我们所说的有序有两个概念,第一个是添加顺序,第二个是大小顺序(实际上就是元素值的大小) List下面重点关注两个实现类分别是: ArrayList LinkedList ArrayList ArrayList底层实现是数组,既然

Java并发(四):并发集合ConcurrentHashMap的源码分析

之前介绍了Java并发的基础知识和使用案例分析,接下来我们正式地进入Java并发的源码分析阶段,本文作为源码分析地开篇,源码参考JDK1.8 OverView: JDK1.8源码中的注释提到:ConcurrentHashMap是一种提供完整的并发检索和对于并发更新有高预测性的散列表,遵循了与HashMap相同的功能性规格,并包含与HashTable每个方法都对应的方法.虽然所有操作都是线程安全的,但检索操作并不牵涉任何锁,不支持任何锁住整个散列表来保护所有的访问. ConcurrentHashM

ThreadLocal定义、使用案例及源码分析

原文连接:(http://www.studyshare.cn/blog-front//blog/details/1165/0 ) 一.ThreadLocal定义 jdk官方文档定义是:该类提供线程局部变量. 这些变量与其正常的对应方式不同,因为访问一个线程(通过其@code get或@code set方法)的每个线 程都有自己的独立初始化变量副本. 通俗来讲就是:使用ThreadLocal包装后的对象,在ThreadLocal所在线程中会有一个对象副本,该副本只会在拥有它的线程中使用,别的线程无

别翻了,这篇文章绝对让你深刻理解java类的加载以及ClassLoader源码分析【JVM篇二】

目录 1.什么是类的加载(类初始化) 2.类的生命周期 3.接口的加载过程 4.解开开篇的面试题 5.理解首次主动使用 6.类加载器 7.关于命名空间 8.JVM类加载机制 9.双亲委派模型 10.ClassLoader源码分析 11.自定义类加载器 12.加载类的三种方式 13.总结 14.特别注意 @ 前言 你是否真的理解java的类加载机制?点进文章的盆友不如先来做一道非常常见的面试题,如果你能做出来,可能你早已掌握并理解了java的类加载机制,若结果出乎你的意料,那就很有必要来了解了解j

7.Java集合-Arrays类实现原理及源码分析

Java集合---Arrays类源码解析 转自:http://www.cnblogs.com/ITtangtang/p/3948765.html 一.Arrays.sort()数组排序 Java Arrays中提供了对所有类型的排序.其中主要分为Primitive(8种基本类型)和Object两大类. 基本类型:采用调优的快速排序: 对象类型:采用改进的归并排序. 1.对于基本类型源码分析如下(以int[]为例): Java对Primitive(int,float等原型数据)数组采用快速排序,对

Java 集合系列(四)—— ListIterator 源码分析

以脑图的形式来展示Java集合知识,让零碎知识点形成体系 Iterator 对比   Iterator(迭代器)是一种设计模式,是一个对象,用于遍历集合中的所有元素.  Iterator 包含四个方法,分别是:next().hasNext().remove().forEachRemaining(Consumer<? super E> action)   Collection 接口继承 java.lang.Iterable,因此所有 Collection 实现类都拥有 Iterator 迭代能力

Java——ArrayList底层源码分析

1.简介 ArrayList 是最常用的 List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问.数组的缺点是每个元素之间不能有间隔, 当数组大小不满足时需要增加存储能力,就要将已经有数组的数据复制到新的存储空间中. 当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制.移动.代价比较高.因此,它适合随机查找和遍历,不适合插入和删除. 线性表的顺序存储,插入删除元素的时间复杂度为O(n),求表长以及增加元素,取第 i 元素的时间复杂度为O(1). ArrayL