ThreadLocal 工作原理、部分源码分析

合集下载

thread local原理

thread local原理

thread local原理Thread Local原理解析1. 什么是Thread Local?Thread Local是一种Java中的线程封闭技术,用于在多线程环境下实现线程私有变量的存储和访问。

每个线程都拥有自己独立的Thread Local变量,互不影响。

2. 为什么需要Thread Local?在多线程编程中,线程之间的共享数据可能会导致竞态条件和数据不一致的问题。

为了避免这些问题,可以使用Thread Local实现每个线程拥有独立的变量副本,从而提高并发性能和数据的一致性。

3. Thread Local的原理Thread Local的内部实现Thread Local通过两个类实现:ThreadLocal和ThreadLocalMap。

•ThreadLocal类负责创建和管理每个线程的ThreadLocal变量。

•ThreadLocalMap类是一个线程级别(Thread Level)的散列表,存储了每个线程的ThreadLocal变量。

Thread Local的工作原理Thread Local的工作原理可以分为以下几个步骤:1.每个Thread对象内部都维护着一个ThreadLocalMap对象。

2.当调用ThreadLocal的set()方法时,实际上是将ThreadLocal对象作为key,将值作为value存储到当前线程的ThreadLocalMap对象中。

3.当调用ThreadLocal的get()方法时,实际上是通过当前线程的ThreadLocalMap对象,以ThreadLocal对象作为key获取对应的值。

4.当线程结束时,ThreadLocalMap中的key-value对会随着线程的结束而被清除,防止内存泄漏。

Thread Local的应用场景Thread Local广泛应用于以下场景:•多线程环境下的数据共享问题。

•每个线程需要独立保存一些变量的场景,如用户身份信息、数据库连接等。

threadlocal 数据结构和工作原理

threadlocal 数据结构和工作原理

threadlocal 数据结构和工作原理
ThreadLocal是Java中的一个线程局部变量,每个线程拥有一
个独立的副本,互不干扰。

ThreadLocal的工作原理是通过ThreadLocal类中的ThreadLocalMap来实现的,ThreadLocalMap是一个自定义的
哈希表,它的key是ThreadLocal对象,value是对应线程的变
量副本。

当我们通过ThreadLocal的get方法获取变量时,会先获取当
前线程,然后通过当前线程获取ThreadLocalMap对象,再通
过ThreadLocal对象作为key来获取对应的变量副本。

当我们通过ThreadLocal的set方法设置变量时,会先获取当
前线程,然后通过当前线程获取ThreadLocalMap对象,再通
过ThreadLocal对象作为key来设置变量副本。

当我们通过ThreadLocal的remove方法移除变量时,同样会
先获取当前线程,然后通过当前线程获取ThreadLocalMap对象,再通过ThreadLocal对象作为key来移除对应的变量副本。

总结起来,ThreadLocal的工作原理可以归纳为以下几个步骤:
1. 获取当前线程;
2. 通过当前线程获取ThreadLocalMap对象;
3. 通过ThreadLocal对象作为key来获取、设置或移除对应的
变量副本。

由于每个线程都拥有自己的ThreadLocalMap对象,所以不同线程之间的变量互不干扰,实现了线程之间的数据隔离,这就是ThreadLocal的核心功能。

Java多线程之深入解析ThreadLocal和ThreadLocalMap

Java多线程之深入解析ThreadLocal和ThreadLocalMap

Java多线程之深⼊解析ThreadLocal和ThreadLocalMap ThreadLocal概述ThreadLocal是线程变量,ThreadLocal中填充的变量属于当前线程,该变量对其他线程⽽⾔是隔离的。

ThreadLocal为变量在每个线程中都创建了⼀个副本,那么每个线程可以访问⾃⼰内部的副本变量。

它具有3个特性:1. 线程并发:在多线程并发场景下使⽤。

2. 传递数据:可以通过ThreadLocal在同⼀线程,不同组件中传递公共变量。

3. 线程隔离:每个线程变量都是独⽴的,不会相互影响。

在不使⽤ThreadLocal的情况下,变量不隔离,得到的结果具有随机性。

public class Demo {private String variable;public String getVariable() {return variable;}public void setVariable(String variable) {this.variable = variable;}public static void main(String[] args) {Demo demo = new Demo();for (int i = 0; i < 5; i++) {new Thread(()->{demo.setVariable(Thread.currentThread().getName());System.out.println(Thread.currentThread().getName()+" "+demo.getVariable());}).start();}}}输出结果:Thread-2 Thread-2Thread-4 Thread-4Thread-1 Thread-2Thread-0 Thread-2Thread-3 Thread-3View Code在不使⽤ThreadLocal的情况下,变量隔离,每个线程有⾃⼰专属的本地变量variable,线程绑定了⾃⼰的variable,只对⾃⼰绑定的变量进⾏读写操作。

ThreadLocal作用以及原理解析

ThreadLocal作用以及原理解析

ThreadLocal作⽤以及原理解析ThreadLocal作⽤对于多个线程访问⼀个共享变量的时候,我们往往要通过加锁的⽅式进⾏同步,像这样但是除此之外,其实还有另⼀种⽅式可以隔绝线程对于共享变量读写的独⽴性。

那就是ThreadLocal。

如果你创建了⼀个ThreadLocal变量,那么访问这个变量的每个线程都会有⼀块独⽴的空间,当多个线程操作这个变量的时候,实际上操作的都是⾃⼰线程所属的空间的那个变量,不会对其他线程有影响,也不会被其他线程影响,因为彼此都是互相独⽴的。

因此想要保证线程安全,也可以把共享变量放在ThreadLocal中。

总体来说就是,ThreadLocal提供了线程内存储变量的能⼒,这些变量不同之处在于每⼀个线程读取的变量是对应的互相独⽴的。

通过get和set⽅法就可以得到当前线程对应的值。

接下来看⼀个例⼦public class ThreadLocalDemo1 {public static int value = 0;static ThreadLocal<Object> local = new ThreadLocal<>();public static void main(String[] args) {for (int i = 0; i < 5; i++) {new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"---线程初始值:"+local.get());local.set("我是"+Thread.currentThread().getName());System.out.println(Thread.currentThread().getName()+"---线程修改值:"+local.get());}},"线程"+i).start();}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("主线程"+local.get());}}运⾏结果线程1---线程初始值:null线程4---线程初始值:null线程3---线程初始值:null线程0---线程初始值:null线程2---线程初始值:null线程0---线程修改值:我是线程0线程3---线程修改值:我是线程3线程4---线程修改值:我是线程4线程1---线程修改值:我是线程1线程2---线程修改值:我是线程2主线程null上⾯这段代码,运⾏结果印证了我们开头说的那些关于ThreadLocal的论述,分析⼀下代码,可以看到,我们这⾥只有⼀个ThreadLocal对象,即local,我们⼀共有五个线程,线程0对local进⾏set值之后,线程2再get却还是null,但是线程0⾃⼰再get,却可以拿到⾃⼰设置的那个值我是线程0 ,别的线程是拿不到这个值的,⽽且代码的最后,在所有的线程都运⾏完毕之后,在主线程对local进⾏get操作,拿到的值却还是null。

JavaThreadLocal原理解析以及应用场景分析案例详解

JavaThreadLocal原理解析以及应用场景分析案例详解

JavaThreadLocal原理解析以及应⽤场景分析案例详解⽬录ThreadLocal的定义ThreadLocal的应⽤场景ThreadLocal的demoTheadLocal的源码解析ThreadLocal的set⽅法ThreadLocal的get⽅法ThreadLocalMap的结构ThreadLocalMap的set⽅法ThreadLocalMap的getEntry⽅法ThreadLocal的内存泄露如何避免内存泄露呢应⽤实例实际应⽤⼆总结ThreadLocal的定义JDK对ThreadLocal的定义如下:TheadLocal提供了线程内部的局部变量:每个线程都有⾃⼰的独⽴的副本;ThreadLocal实例通常是类中的private static字段,该类⼀般与线程状态相关(或线程上下⽂)中使⽤。

只要线程处于活动状态且ThreadLocal实例时可访问的状态下,每个线程都持有对其线程局部变量的副本的隐式引⽤,在线程消亡后,ThreadLocal实例的所有副本都将进⾏垃圾回收。

ThreadLocal的应⽤场景ThreadLocal 不是⽤来解决多线程访问共享变量的问题,所以不能替换掉同步⽅法。

⼀般⽽⾔,ThreadLocal的最佳应⽤场景是:按照线程多实例(每个线程对应⼀个实例)的对象的访问。

例如:在事务中,connection绑定到当前线程来保证这个线程中的数据库操作⽤的是同⼀个connection。

ThreadLocal的demopublic class ThreadLocalTest {public static void main(String[] args) {ThreadLocal<String> threadLocal = new ThreadLocal<>();threadLocal.set("张三");new Thread(()->{threadLocal.set("李四");System.out.println("*******"+Thread.currentThread().getName()+"获取到的数据"+threadLocal.get());},"线程1").start();new Thread(()->{threadLocal.set("王⼆");System.out.println("*******"+Thread.currentThread().getName()+"获取到的数据"+threadLocal.get());},"线程2").start();new Thread(()->{System.out.println("*******"+Thread.currentThread().getName()+"获取到的数据"+threadLocal.get());},"线程3").start();System.out.println("线程=" + Thread.currentThread().getName() + "获取到的数据=" + threadLocal.get());}}运⾏结果:从运⾏结果,我们可以看出线程1和线程2在ThreadLocal中设置的值相互独⽴,每个线程只能取到⾃⼰设置的那个值。

ThreadLocal 源码解读

ThreadLocal 源码解读

ThreadLocal 源码解读基本概念当访问共享的可变数据时,通常需要使用同步。

一种避免使用同步的方式就是不共享数据。

如果仅在单线程内访问数据,就不需要同步。

这种技术被称为线程封闭Thread Confinement。

当某个对象封闭在一个线程中时,这种用法将自动实现线程安全性,即使被封闭的对象本身不是线程安全的。

线程封闭其中一种实现方式就是ThreadLocal的方式,简单说,就是对于一个共享变量为每个线程都保存了一个副本,ThreadLocal为每个使用该变量的线程提供独立的变量副本,使之成为独立的线程局部变量。

ThreadLocal模式解决的是同一线程中隶属于不同开发层次的数据共享问题,而不是在不同的开发层次中进行数据传递。

使用ThreadLocal模式,可以使得数据在不同的编程层次得到有效地共享。

从上面图中我们可以看到,由于ThreadLocal所操作的是维持于整个Thread生命周期的副本(ThreadLocalMap),所以无论在J2EE程序程序的哪个层次(表示层、业务逻辑层或者持久层),只要在一个Thread的生命周期之内,存储于ThreadLocalMap中的对象都是线程安全的(因为ThreadLocalMap本身仅仅隶属于当前的执行线程,是执行线程内部的一个属性变量。

我们用图中的阴影部分来表示这个变量的存储空间)。

而这一点,正是被我们用于来解决多线程环境中的变量共享问题的核心技术。

ThreadLocal的这一特性也使其能够被广泛地应用于J2EE开发中的许多业务场景。

摘自:/blog/2295284ThreadLocal使用是比较直观的,本次的重点也不在于使用,而在内部的实现方式。

实现源码Thread类内部维护了ThreadLocalMap结构,但是ThreadLocalMap的维护交给了ThreadLocal 对象public class Thread{// 由于使用了内部类,Thread对象有threadLocals引用,但是操作权在ThreadLocal对象手中ThreadLocal.ThreadLocalMap threadLocals = null;}//ThreadLocalMap是定义在Thread中,内部维护了一个Entry数组/*** ThreadLocalMap is a customized hash map suitable only for* maintaining thread local values. No operations are exported* outside of the ThreadLocal class. The class is package private to* allow declaration of fields in class Thread. To help deal with* very large and long-lived usages, the hash table entries use* WeakReferences for keys. However, since reference queues are not* used, stale entries are guaranteed to be removed only when* the table starts running out of space.*/static class ThreadLocalMap {//每个ThreadLocal对象都维护了一个threadLocalHashCode,根据这个值和ThreadLocalMap的容量进行交运算来确定下标private Entry[] table;ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY);}}//实际上Entry是对ThrealLocal的封装/*** 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是对ThreadLocal的简单封装,继承了WeakReference,使用WeakReference的好处是,reference实例不会影响到被应用对象的GC回收行为(即只要对象被WeakReference对象之外所有的对象解除引用后,该对象可以被GC回收)只不过在被对象回收之后,reference实例想获得被应用的对象时程序会返回null。

Java并发编程:ThreadLocal的使用以及实现原理解析

Java并发编程:ThreadLocal的使⽤以及实现原理解析前⾔前⾯的⽂章⾥,我们学习了有关锁的使⽤,锁的机制是保证同⼀时刻只能有⼀个线程访问临界区的资源,也就是通过控制资源的⼿段来保证线程安全,这固然是⼀种有效的⼿段,但程序的运⾏效率也因此⼤⼤降低。

那么,有没有更好的⽅式呢?答案是有的,既然锁是严格控制资源的⽅式来保证线程安全,那我们可以反其道⽽⾏之,增加更多资源,保证每个线程都能得到所需对象,各⾃为营,互不影响,从⽽达到线程安全的⽬的,⽽ThreadLocal便是采⽤这样的思路。

ThreadLocal实例ThreadLocal翻译成中⽂的话⼤概可以说是:线程局部变量,也就是只有当前线程能够访问。

它的设计作⽤是为每⼀个使⽤该变量的线程都提供⼀个变量值的副本,每个线程都是改变⾃⼰的副本并且不会和其他线程的副本冲突,这样⼀来,从线程的⾓度来看,就好像每个线程都拥有了该变量。

下⾯是⼀个简单的实例:public class ThreadLocalDemo {static ThreadLocal<Integer> local = new ThreadLocal<Integer>(){@Overrideprotected Integer initialValue() {return 0;}};public static class MyRunnable implements Runnable{@Overridepublic void run() {for (int i = 0;i<3;i++){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}int value = local.get();System.out.println(Thread.currentThread().getName() + ":" + value);local.set(value + 1);}}}public static void main(String[] args) {MyRunnable runnable = new MyRunnable();Thread t1 = new Thread(runnable);Thread t2 = new Thread(runnable);t1.start();t2.start();}}上⾯的代码不难理解,⾸先是定义了⼀个名为local的ThreadLocal变量,并初识变量的值为0,然后是定义了⼀个实现Runnable接⼝的内部类,在其run⽅法中对local的值做读取和加1的操作,最后是main⽅法中开启两个线程来运⾏内部类实例。

ThreadLocal的原理、作用、使用弱引用原因、应用举例

ThreadLocal的原理、作⽤、使⽤弱引⽤原因、应⽤举例⼀. 原理ThreadLocal就是⼀个类,他有get、set⽅法,可以起到⼀个保存、获取某个值的作⽤。

但是这个类的get、set⽅法有点特殊,各个线程调⽤时是互不⼲扰的,就好像线程在操作ThreadLocal对象时是在操作线程⾃⼰的私有属性⼀样。

具体原因在于他的⽅法实现:public T get() {Thread t = Thread.currentThread(); //先确定调⽤我的线程ThreadLocalMap map = getMap(t); //根据调⽤我的线程,找到这个线程的ThreadLocalMap对象if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this); //以ThreadLocal对象为key,找到对应元素if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value; //讲元素的value返回return result;}}return setInitialValue(); //如果调⽤我的线程没有ThreadLocalMap对象,则返回初始值}public void set(T value) {Thread t = Thread.currentThread(); //先确定调⽤我的是哪个线程ThreadLocalMap map = getMap(t); //获取调⽤我的线程的ThreadLocalMapif (map != null)map.set(this, value); //如果那个线程有map,就将此ThreadLocal对象为key的value设置好elsecreateMap(t, value); //如果那个线程还没有map,先创建⼀个再设置}ThreadLocalMap是ThreadLocal的内部类,为了不造成混乱,可以把他看作⼀个普通的类。

ThreadLocal系列(三)-TransmittableThreadLocal的使用及原理解析

ThreadLocal系列(三)-TransmittableThreadLocal的使⽤及原理解析上⼀篇:本篇⽂档已转移⾄新博客,请点击前往:⼀、基本使⽤⾸先,TTL是⽤来解决ITL解决不了的问题⽽诞⽣的,所以TTL⼀定是⽀持⽗线程的本地变量传递给⼦线程这种基本操作的,ITL也可以做到,但是前⾯有讲过,ITL在线程池的模式下,就没办法再正确传递了,所以TTL做出的改进就是即便是在线程池模式下,也可以很好的将⽗线程本地变量传递下去,先来看个例⼦:// 需要注意的是,使⽤TTL的时候,要想传递的值不出问题,线程池必须得⽤TTL加⼀层代理(下⾯会讲这样做的⽬的)private static ExecutorService executorService = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));private static ThreadLocal tl = new TransmittableThreadLocal<>(); //这⾥采⽤TTL的实现public static void main(String[] args) {new Thread(() -> {String mainThreadName = "main_01";tl.set(1);executorService.execute(() -> {sleep(1L);System.out.println(String.format("本地变量改变之前(1), ⽗线程名称-%s, ⼦线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), tl.get()));});executorService.execute(() -> {sleep(1L);System.out.println(String.format("本地变量改变之前(1), ⽗线程名称-%s, ⼦线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), tl.get()));});executorService.execute(() -> {sleep(1L);System.out.println(String.format("本地变量改变之前(1), ⽗线程名称-%s, ⼦线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), tl.get()));});sleep(1L); //确保上⾯的会在tl.set执⾏之前执⾏tl.set(2); // 等上⾯的线程池第⼀次启⽤完了,⽗线程再给⾃⼰赋值executorService.execute(() -> {sleep(1L);System.out.println(String.format("本地变量改变之后(2), ⽗线程名称-%s, ⼦线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), tl.get()));});executorService.execute(() -> {sleep(1L);System.out.println(String.format("本地变量改变之后(2), ⽗线程名称-%s, ⼦线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), tl.get()));});executorService.execute(() -> {sleep(1L);System.out.println(String.format("本地变量改变之后(2), ⽗线程名称-%s, ⼦线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), tl.get()));});System.out.println(String.format("线程名称-%s, 变量值=%s", Thread.currentThread().getName(), tl.get()));}).start();new Thread(() -> {String mainThreadName = "main_02";tl.set(3);executorService.execute(() -> {sleep(1L);System.out.println(String.format("本地变量改变之前(3), ⽗线程名称-%s, ⼦线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), tl.get()));});executorService.execute(() -> {sleep(1L);System.out.println(String.format("本地变量改变之前(3), ⽗线程名称-%s, ⼦线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), tl.get()));});executorService.execute(() -> {sleep(1L);System.out.println(String.format("本地变量改变之前(3), ⽗线程名称-%s, ⼦线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), tl.get()));});sleep(1L); //确保上⾯的会在tl.set执⾏之前执⾏tl.set(4); // 等上⾯的线程池第⼀次启⽤完了,⽗线程再给⾃⼰赋值executorService.execute(() -> {sleep(1L);System.out.println(String.format("本地变量改变之后(4), ⽗线程名称-%s, ⼦线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), tl.get()));});executorService.execute(() -> {sleep(1L);System.out.println(String.format("本地变量改变之后(4), ⽗线程名称-%s, ⼦线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), tl.get()));});executorService.execute(() -> {sleep(1L);System.out.println(String.format("本地变量改变之后(4), ⽗线程名称-%s, ⼦线程名称-%s, 变量值=%s", mainThreadName, Thread.currentThread().getName(), tl.get()));});System.out.println(String.format("线程名称-%s, 变量值=%s", Thread.currentThread().getName(), tl.get()));}).start();}private static void sleep(long time) {try {Thread.sleep(time);} catch (InterruptedException e) {e.printStackTrace();}}运⾏结果:线程名称-Thread-2, 变量值=4本地变量改变之前(3), ⽗线程名称-main_02, ⼦线程名称-pool-1-thread-1, 变量值=3线程名称-Thread-1, 变量值=2本地变量改变之前(1), ⽗线程名称-main_01, ⼦线程名称-pool-1-thread-2, 变量值=1本地变量改变之前(1), ⽗线程名称-main_01, ⼦线程名称-pool-1-thread-1, 变量值=1本地变量改变之前(3), ⽗线程名称-main_02, ⼦线程名称-pool-1-thread-2, 变量值=3本地变量改变之前(3), ⽗线程名称-main_02, ⼦线程名称-pool-1-thread-2, 变量值=3本地变量改变之前(1), ⽗线程名称-main_01, ⼦线程名称-pool-1-thread-1, 变量值=1本地变量改变之后(2), ⽗线程名称-main_01, ⼦线程名称-pool-1-thread-2, 变量值=2本地变量改变之后(4), ⽗线程名称-main_02, ⼦线程名称-pool-1-thread-1, 变量值=4本地变量改变之后(4), ⽗线程名称-main_02, ⼦线程名称-pool-1-thread-1, 变量值=4本地变量改变之后(4), ⽗线程名称-main_02, ⼦线程名称-pool-1-thread-2, 变量值=4本地变量改变之后(2), ⽗线程名称-main_01, ⼦线程名称-pool-1-thread-1, 变量值=2本地变量改变之后(2), ⽗线程名称-main_01, ⼦线程名称-pool-1-thread-2, 变量值=2程序有些啰嗦,为了说明问题,加了很多说明,但⾄少通过上⾯的例⼦,不难发现,两个主线程⾥都使⽤线程池异步,⽽且值在主线程⾥还发⽣过改变,测试结果展⽰⼀切正常,由此可以知道TTL在使⽤线程池的情况下,也可以很好的完成传递,⽽且不会发⽣错乱。

java中threadlocal底层原理

Java中ThreadLocal底层原理一、概述ThreadLocal是Java中一个非常重要的多线程并发工具类,它提供了一种线程级别的数据隔离机制。

通过ThreadLocal,我们可以在多线程环境下,为每个线程维护一个独立的变量副本,从而避免了线程安全问题。

二、ThreadLocal的基本使用在Java中,我们可以通过以下步骤来使用ThreadLocal: 1. 创建一个ThreadLocal对象。

2. 通过ThreadLocal的set方法,为当前线程设置一个值。

3. 通过ThreadLocal的get方法,获取当前线程的值。

下面是一个简单的示例代码:public class ThreadLocalDemo {private static ThreadLocal<String> threadLocal = new ThreadLocal<>();public static void main(String[] args) {// 在主线程中设置值threadLocal.set("Hello, World!");// 在子线程中获取值Thread thread = new Thread(() -> {String value = threadLocal.get();System.out.println("子线程获取到的值:" + value);});thread.start();// 在主线程中获取值String value = threadLocal.get();System.out.println("主线程获取到的值:" + value);}}三、ThreadLocal的底层原理ThreadLocal的底层原理涉及到了ThreadLocalMap、Thread和ThreadLocal三个关键类。

1. ThreadLocalMap每个Thread对象内部都维护了一个ThreadLocalMap对象,它是一个自定义的哈希表,用于存储ThreadLocal对象与其对应的值。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

ThreadLocal 工作原理、部分源码分析1.大概去哪里看ThreadLocal 其根本实现方法,是在Thread里面,有一个ThreadLocal.ThreadLocalMap属性ThreadLocal.ThreadLocalMap threadLocals = null;ThreadLocalMap 静态内部类维护了一个Entry 数组1private Entry[] table;查看Entry 源码,它维护了两个属性,ThreadLocal 对象与一个Object复制代码static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}复制代码那么,这几项似乎可以这么串下来:Thread. currentThread().threadLocals. table{当前线程,的ThreadLocalMap对象,的Entry数组}(忽略访问权限的事儿)------------------------------------------------我是分割线------------------------------------------------2.代码实现分析ThreadLocal 提供set(),get()方法,用于数据的写入与读取。

数据的存储与获取的位置,即Thread. currentThread().threadLocals. table {当前线程,的ThreadLocalMap对象,的Entry数组}复制代码public void set(T value) {Thread t = Thread.currentThread();//获取当前线程tThreadLocalMap map = getMap(t);//获取threadLocals 对象if (map != null)map.set(this, value);//调用ThreadLocalMap 的set方法向threadLocals 中写入一条数据elsecreateMap(t, value);//如果threadLocals 为null 则为当前线程t 创建一个map,并插入数据}复制代码map.set(this, value);注意,这里,传入的第一个参数为this 即ThreadLocal 对象自身,假如我们声明了一串代码:1private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();然后我们又执行了threadLocal.set(“string 1234”);那么,在Thread. currentThread().threadLocals. table 中,应该有这么一个Entry :ThreadLocal指向threadLocal,value 为“string 1234”分析源码(这里,所有的源码都来自于jdk1.7.0_71):复制代码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[] 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;}if (k == null) {replaceStaleEntry(key, value, i);return;}}tab[i] = new Entry(key, value);int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();}复制代码分析两处:1. int i = key.threadLocalHashCode & (len-1);根据当前的ThreadLocal 的threadLocalHashCode 跟ThreadLocalMap.table的长度-1 ,按位与,获得目标索引值i ,如果tab[i] 为空的话,将会在tab[i] 处插入一个Entry ;2. for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)])如果如果tab[i] 不为空,则调用i = nextIndex(i, len) 将i值进行+1或者置为0,然后判断e是否为null 如果e!=null 判断e 中的ThreadLocal对象,跟传入的ThreadLocal 对象,是否为同一个对象。

如果是同一个对象,则对e的value 进行重新赋值。

如果在遍历的过程中发现某个e的ThreadLocal 对象为空,则将Entry(threadLocal,”string 1234”) 设置在此时的tab[i]处。

(如果一开始进来的时候e 为null 即tab[i]==null 。

是不会走for循环的,会直接把Entry(threadLocal,”string 1234”) 赋值到table[i]);123private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);}分析,为什么会有i = nextIndex(i, len) 这样的设定。

执行int i = key.threadLocalHashCode & (len-1);的时候,很可能不同的key.threadLocalHashCode得到了相同的i 值,那么,就从i 开始,遍历table对象,找到一个可以放置Entry(threadLocal,”string 1234”) 的位置,比如:System.out.println(626627285 & 16-1);//5System.out.println(626627317 & 16-1);//5System.out.println(626627573 & 16-1);//5这三个,获取到的i值,都为5(当然实际用到的hashCode的算法不是这样的,不会产生这么接近的数)。

在同一个线程中,626627285先set(value1)了,得到5,table[5]为空,那就填进去table[5]=new Entry(626627285,value1);626627317接着set(value2),算出来i=5,但是table[5]已经有人占了,那就只能看table[6]有没有空闲位置,一看table[6]==null,好,就放这儿了table[6]=new Entry(626627317,value2);626627573接着set(value3),算出来i=5,但是table[5]已经有人占了,那就只能看table[6]有没有空闲位置,一看table[6]也被占了,再看table[7]==null,好,就放这儿了table[7]=new Entry(626627573,value3)假如有个线程算出来i=15 但是table[15]!=null,需要向后找空闲位置,table[16]是越界的,nextIndex返回0,从table[0]开始找下面这串代码,维护了table 的长度,避免了遍历了一圈table 却找不到table[i]==null 的情况,即保证table的某些索引处肯定为null,因为还没填满的时候就已经扩容了。

if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();------------------------------------------------我是分割线------------------------------------------------下面分析get()复制代码public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();}复制代码1. 获取当前线程的threadLocals 并传入ThreadLocal 对象,获取对应的值。

2. 如果当前线程的threadLocals 为null ,则为当前线程t 创建一个map,并插入数据setInitialValue ()=null,并返回null复制代码private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;}复制代码分析map.getEntry(this)复制代码private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.get() == key)return e;elsereturn getEntryAfterMiss(key, i, e);}复制代码这里看到,也是先使用int i = key.threadLocalHashCode & (table.length - 1); 得到一个索引值,然后去table获取Entry对象,得到几种结果:1. e!=null && e.get()!=key 因为是通过“int i = key.threadLocalHashCode & (table.length - 1);”获取的索引值,不同的ThreadLocal 对象,可能获取到相同的索引值,所以,这种情况是存在的。

相关文档
最新文档