一、序言
这里会分析ThreadLocal 源码以及原理,和它的正确使用原则,还有我们应用过的地方,帮助大家更深刻的理解这个类的使用。
ThreadLocal 在JDK1.2的版本的就提供的一个类,它提供了一种新的思路去解决多线程问题,同时ThreadLocal 不是线程类,仅仅是一个线程的变量副本,他是如何来实现这个功能的呢,我们从源码进行分析。
二、源码分析
// 仅仅是一个单独的类,没有除Object外的其他父类 public class ThreadLocal<T> {...}
我们还是从基本的API 方法分析,其中包括get(),
initialValue(),
remove(),
set(T value) 方法:
2.1 get() : 返回此线程局部变量的当前线程副本中的值
public T get() { // 回先获得当前线程 Thread t = Thread.currentThread(); // 然后获得线程t 里面的变量threadLocals,看下面的方法getMap() // 这里变量其实是个map,具体实现,我们先分析几本原理,再分析实现 ThreadLocalMap map = getMap(t); if (map != null) { // 然后获得当前里面的值,这里可以参考hashMap 的实现原理 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } // 如果没有获得值,表示还没set 值,就会返回初始值 // 这里是初始化ThreadLocal 的方法。 return setInitialValue(); } // 注意:这里是从Thread 获得的变量 // 关于ThreadLocalMap是个什么东西,我们先介绍几本原理,在详细分析。 ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
2.2 initialValue()
返回此线程局部变量的当前线程的“初始值”。
这是源码,我们可以这样使用
protected T initialValue() { return null; }
// 这是API 介绍的,我们可以按类似的方式给它赋初始值 // 可以看出这个是protected 的initialValue 准备让子类重写的 // 这个值仅仅作为初始化用,当第一次执行set 方法的时候,就会覆盖这个值,但是初始值始终存在。 // 让你remove 的之后,在次进行get 就会返回初始值 private static final ThreadLocal <Integer> uniqueNum = new ThreadLocal<Integer> () { @Override protected Integer initialValue() { return 1; } };
2.3 set(T value)
将此线程局部变量的当前线程副本中的值设置为指定值。
public void set(T value) { // 同样是获得当线程,和里面的变量map.然后set 值 Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } // 这里变量第一次为 null 的情况,会创建一个新对象。 void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
// 可以看出这是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.4 remove()
移除此线程局部变量当前线程的值。
public void remove() { // 这里也是传入当前线程,然后操作变量map ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
结论:从上面的基本方法可以看出,我们都是获得Thread.currentThread(),然后操作里面的一个ThreadLocalMap类型的变量完成了保存对象的任务,这也就完成了和线程之间的绑定,至于为什么是线程的一个变量呢,我这里再帖一下Thread的源码。
public class Thread implements Runnable { /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ // 可以看出Thread 类,对 ThreadLocalMap 的引用。 ThreadLocal.ThreadLocalMap threadLocals = null; }
那么我们现在看看 ThreadLocalMap 到底是干嘛的呢?
三、ThreadLocalMap 源码分析
// 这是ThreadLocal的一个内部类 static class ThreadLocalMap { // 这又是一个内部类,继承与弱引用,至于弱引用这里暂时不详细介绍 // 可以参考JVM 里面的的引用类型 和 GC回收机制 // 看过hashMap 源码的人,肯定比较熟悉这种写法 // 实现的是一种K,V 的内部类机制 static class Entry extends WeakReference<ThreadLocal> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } } }
3.1 构造函数
// 这里参考hashMap 的方式设计,就不多多介绍了 // 一个放着entry 的数组table ThreadLocalMap(ThreadLocal firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; // 这里hashCode 的取值比较特别,我们单独分析 // 根据hashCode 和长度取余运算 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); }
3.2 特殊的hashCode 值
// 这里看出,这个hashCode 值在创建ThreadLocal 对象的时候就创建了 private final int threadLocalHashCode = nextHashCode(); // 1640531527 // 这里为什么要用这个数字,我还真不清楚,但是能确定是为了更均匀的分布 // 这个和hashMap 的 hashCode 计算方式一样,应该都有进行专门的测试,以后再研究这个 private static final int HASH_INCREMENT = 0x61c88647; private static AtomicInteger nextHashCode = new AtomicInteger(); // 这里我们可以看出hashCode 居然是不停的加上HASH_INCREMENT 进行 private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }
3.3 set 方法:这里是内部类如何存放值得
private void set(ThreadLocal key, Object value) { 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; } // 如果存放的k为null,可能被回收了,也就是过去了嘛 // 这里就把以前这个位置的信息覆盖,以前的就没了。 if (k == null) { replaceStaleEntry(key, value, i); return; } } // 都没有的情况下,从新创建一个 tab[i] = new Entry(key, value); int sz = ++size; // 这里又会检测过期元素,并删除过期的, // 如果没有过期的,超过限制范围,就会扩容 if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
3.4 getEntry 方法:返回这个对象,就能获得值
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); } private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { ThreadLocal k = e.get(); if (k == key) return e; if (k == null) // 删除空的 expungeStaleEntry(i); else // 继续找,这里应该是用的线性探查法解决冲突的 i = nextIndex(i, len); e = tab[i]; } return null; }
3.5 remove 方法,删除元素
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) { // 这里删除仅仅是引用设置为null 就行了。 e.clear(); expungeStaleEntry(i); return; } } }
结论:我们可以看出内部类的实现,是对一个继承了弱应用的的K.V entry 的操作。我们看出每一次操作都会去检测一那些对象为空了,然后进行删除。同时也看出了它的操作虽然类似map,但是却不是操作的map.关于弱引用的作用我这里简答描述是:当某对象已经不使用了,为null的情况下,为了不让强引用占有这个空间,那么弱引用能加快GC的一种手段。
四、应用场景
4.1 描述:
ThreadLocal 提供一种新的思路去解决多线程问题,是解决什么问题呢?又是如何解决呢?
在多线程并发中,我们常常遇到的问题是共享资源的操作,常用的办法是加锁机制,但是这种机制负面影响比较多。然后ThreadLocal 提供一种保存带状态的共享变量的副本的方式,来隔绝各个线程中带来的影响,以空间换时间。
4.2 实例1:
比如:Spring 中,我们常用的单利模式,假设我一个Count 类,里面有个属性num,用来观察某一时刻的浏览数量。
public static class Count{ public static int num = 0; }
在多线程中,不是使用加锁等手段,如果A 线程在A时刻先将num 变成100,然后准备访问之前,这时候B线程在B时刻将num 变成200.然后在由A线程去访问,返回200,明显是有错误的。当然创建多个Count也可以解决,那么我们在不创建的多个Count 对象的情况下,如何保证安全呢?
我们可以在A B线程创建一个ThreadLocal 变量,然后当A线程将num 变成100的时候,同时将这这个100的值,存放在ThreadLocal 里面,那么下次访问就从变量里面提取,就不会错误了,当然由于业务原因,这里例子不太好,但是道理类似。
实例 2:这里我copy 的网上hibernate 里面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; }
可以看出,每个线程保存一份session,方便下次使用,同时各自有各自的状态,同时也会随着线程的退出而快速的清理这个副本。
实例3 :这里简单的原理,我直接贴下代码,大家感受一下:
static class A { public boolean flag = false; } public static void main(String[] args) throws InterruptedException { ThreadLocal thread = new ThreadLocal(); A a = new A(); thread.set(a.flag); a.flag = true; // 虽然一个线程,但是变量值是可以做个隔离的 System.out.println(thread.get() +":"+a.flag); }
五、Thread 和 ThreadLocal 关系和原理:
// 这里我们已经说过每个Thread 里面都有 引用,变量副本主要是通过 // 操作内部类进行实现的 ThreadLocal.ThreadLocalMap threadLocals = null; // 这个变量作用是为可以子线程访问父线程的变量而准备的。 // 这里我们暂时不做详细介绍,原理都差不多 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
下面我再看看线程退出的时候,默认会调exit()方法:
private void exit() { if (group != null) { group.remove(this); group = null; } // 这里看到, 线程退出,会将这个设置为空 // 由前的源码分析可以看出,当key 为空的时候会被清除 // 加快GC threadLocals = null; inheritableThreadLocals = null; inheritedAccessControlContext = null; blocker = null; uncaughtExceptionHandler = null; }
总结:
1.ThreadLocal我仅仅分析了大部分源码, 还有一部分没分析,感兴趣的朋友可以共同讨论
2.强调两点:
a.ThreadLocal不是线程,仅仅是存放变量的的一个副本,每个Thread都有引用
b.ThreadLocal实现不是map,虽然类似!它主要用来存放有变化的状态,用于隔离多线程的影响
3.JDK 建议创建以static final 为主,节约内存。
4.关于使用原则,只能大家对原理机制理解了,就知道什么地方该用了
5.上面仅限于自己理解,如果有异议的地方请指出,还有不懂,或者其他想法的朋友,希望多沟通讨论。
相关推荐
首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。
**ThreadLocal概述** ThreadLocal是Java中的一个线程局部变量类,它为每个线程创建了一个独立的变量副本。这意味着每个线程都有自己的ThreadLocal变量,互不干扰,提供了线程安全的数据存储方式。ThreadLocal通常...
通过分析ThreadLocal的源码,我们可以深入理解其内部机制。例如,`initialValue()`方法用于提供默认的初始值,`set()`方法如何将值放入ThreadLocalMap,`get()`方法如何获取值,以及`remove()`方法如何清理线程局部...
ThreadLocal 源码分析和使用 ThreadLocal 是 Java 语言中的一种多线程编程机制,用于解决多线程程序的并发问题。它不是一个 Thread,而是一个 Thread 的局部变量。ThreadLocal 的出现是为了解决多线程程序中的共享...
以上是对ThreadLocal的简单介绍和源码分析,实际使用中还需要结合具体业务场景进行合理设计和优化。通过深入理解ThreadLocal的工作原理,可以帮助我们更好地利用这一工具,提高代码的并发性能和可维护性。
Java并发编程中,ThreadLocal是一个非常重要的类,它是解决线程安全问题的一个工具。ThreadLocal提供了一种在多线程环境下使用“共享变量”的方式,但是它实际上是为每个线程提供一个变量的副本,这样每个线程都可以...
Java并发编程学习之ThreadLocal源码详析 ThreadLocal是Java并发编程中的一种机制,用于解决多线程访问共享变量的问题。它可以使每个线程对共享变量的访问都是线程安全的,使得多线程编程变得更加简单。 ...
`javaThread`项目则是一个专门针对Java并发编程进行源码分析的实战手册,它旨在帮助开发者深入理解Java线程的内部机制和并发工具的实现原理。 在Java中,`Thread`类是所有线程的基础,它是Java并发模型的核心。`...
ThreadLocal 的原理、源码深度分析及使用 ThreadLocal 是 Java 语言中的一种机制,用于实现线程本地存储,能够帮助开发者在多线程环境下解决变量访问安全的问题。下面将对 ThreadLocal 的原理、实现机制、使用方法...
6. **源码分析**: ThreadLocal类的核心在于`initialValue()`方法和`get()`方法。`initialValue()`是线程首次访问ThreadLocal变量时调用的,返回的是默认值。`get()`方法则会查找当前线程的ThreadLocalMap,如果...
5. **源码分析**: - `ThreadLocal`类在`java.lang`包下,它的实现主要依赖于`Thread`类的成员变量`threadLocals`,这是一个`ThreadLocalMap`实例,`ThreadLocalMap`是`WeakReference<ThreadLocal<?>>`和`Object`...
标题所指的知识点为“Druid 源码分析 逐层详解”,意味着我们需要深入分析Druid这一开源数据处理工具的源码,并从不同的层面揭示其内部实现机制。 首先,我们来看Druid的构架设计。Druid采用了分层的架构,每个层次...
源码分析: 在Java的`ThreadLocal`实现中,每个线程都有一个`ThreadLocalMap`,它是`ThreadLocal`的一个内部类,用于存储线程局部变量。`ThreadLocalMap`使用弱引用作为键,目的是在线程不再引用ThreadLocal对象时,...
ThreadLocal的源码分析: 首先是类的介绍。ThreadLocal类提供了线程本地变量。这些变量使每个线程都有自己的一份拷贝。ThreadLocal期望能够管理一个线程的状态,例如用户id或事务id。 ThreadLocal的使用有很多...
此外,分析ThreadLocal内存泄露可以借助一些工具,例如JProfiler、VisualVM或Java自带的JConsole等,它们可以帮助我们观察和定位内存中的异常情况。 了解ThreadLocal的内存泄露机制以及如何避免它,对于编写高效、...
《JUC并发编程与源码分析视频课》是一门深入探讨Java并发编程的课程,主要聚焦于Java Util Concurrency(JUC)库的使用和源码解析。JUC是Java平台提供的一组高级并发工具包,它极大地简化了多线程编程,并提供了更...
在分析ThreadLocal源码时,可以了解到它如何在内部实现线程隔离,以及ThreadLocalMap的结构和工作方式。理解这些细节有助于我们更好地利用ThreadLocal,同时避免可能出现的问题。通过阅读源码,我们可以发现...
**标题:“JDK的ThreadLocal理解(一)使用...通过以上分析,我们可以看到ThreadLocal在实现线程间数据隔离、简化多线程编程方面的作用。然而,使用时也要注意避免内存泄漏和过度依赖,合理规划其在系统架构中的位置。
内容概要:本文深入解析了Java中的ThreadLocal工具类,包括其常见应用场景及其原理和源码分析。通过Demo示例介绍了如何利用ThreadLocal实现线程间的隔离,使各个线程可以拥有独立的变量副本而不互相干扰。文章详细...
`multiThread`是一个开源项目,旨在帮助开发者深入理解Java多线程编程,并提供了源码分析、工具实现以及使用样例。这个学习库是学习和实践Java并发编程的理想资源。 1. **Java多线程基础** - Java中的线程是程序...