点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人。

文章不定期同步公众号,还有各种一线大厂面试原题、我的学习系列笔记。

ThreadLocal的原理

特点
  • ThreadLocal和Sychronized都用于解决多线程间的并发访问,但它们实现的本质方法不同
  • 它们如何实现的:sychronized利用锁使同一个代码块或变量在某时刻只能被一个线程访问;而ThreadLocal给所有线程都提供一个变量副本,每个线程(Thread类)中有属性【ThreadLocal.ThreadLocalMap threadLocals=null】,实质是1个线程通过ThreadLocal这个虚门去获取该线程自带的Map型=ThreadLocal.ThreadLocalMap型属性threadLocals ,不存在则

    【new ThreadLocalMap()】赋值给该线程的属性threadLocals ,这样在某一时刻不同线程访问到的是对应线程的Map型=ThreadLocal.ThreadLocalMap型threadLocals属性,ThreadLocal.ThreadLocalMap也是一个map,其代码结构如下
public class ThreadLocal<T> {
...
static class ThreadLocalMap {
//该table类似于hashmap一样,拿来装value值,每个线程都有一个自己的ThreadLocalMap类,这些value值是每个线程通过共用一个ThreadLocal各自保存到自己的ThreadLocalMap中的table数组里
private Entry[] table;
//可知ThreadLocalMap的内部类Entry继承WeakReference,所以Entry是弱引用,进而引发使用ThreadLocal类时的“内存溢出”问题
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value; Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
} //该方法可以看出ThreadLocal.ThreadLocalMap保存元素时,key为“公用ThreadLocal变量”,value是真正想保存的值
private void set(ThreadLocal<?> key, Object value) {
...
}
}
}
用ThreadLocal存东西

每个线程都执行"公用ThreadLocal变量"的set(value)保存值->

"公用ThreadLocal变量"的set(value)里先currentThread()得到当前线程->

得到当前线程的【ThreadLocal.ThreadLocalMap threadLocals】->

往threadLocals里面存值:

threadLocals.set(公用ThreadLocal变量,value)->

threadLocals的set方法先用“公用ThreadLocal变量”计算table数组的下标,再把value和“公用ThreadLocal变量”都封装在Entry对象里面做属性,最后再把该封装的【Entry{value}】对象保存到table数组该下标处->

如果table数组该下标处已有元素,即冲突了,采用线性探测法不断计算下一个下标

set()方法源码:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
} //线性探测法:ThreadLocal.ThreadLocalMap的属性“private Entry[] table”保存元素发生冲突时采用线性探测法,len为table初始长度16:
private static int nextIndex(int i, int len) {
//可知线性探测每次+1
return ((i + 1 < len) ? i + 1 : 0);
}
取ThreadLocal中的东西

每个线程都执行"公用ThreadLocal变量"的get()方法获取value值->

"公用ThreadLocal变量"的get()方法里先currentThread()得到当前线程->

得到当前线程的【ThreadLocal.ThreadLocalMap threadLocals】->

threadLocals.get(公用ThreadLocal变量)->

threadLocals的get方法里先用“公用ThreadLocal变量”计算table数组的下标,然后直接取该下标处的【Entry{value}】对象->

得到Entry对象后再取出它的value属性就是想要取的值

get()方法源码:
public T get() {
//直接就得到了当前线程
Thread t = Thread.currentThread();
//获取每个线程自有的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//传入this="公用ThreadLocal"去计算ThreadLocalMap的table下标,拿到下标后返回该下标位置的【Entry{value}】对象
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
//直接取【Entry{value}】对象的属性value就是想要的值
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
//map.getEntry(this):传入this="公用ThreadLocal"去计算ThreadLocalMap的table下标,拿到下标后返回该下标位置的【Entry{value}】对象
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 T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}

ThreadLocal的使用场景

使用场景:数据库连接、session管理、父子线程共享线程变量(如token)

如下使用ThreadLocal为每个线程创建数据库连接

import java.sql.Connection;
import java.sq1.Statement;
/**
主线程中定义static型的ThreadLocal,,每个线程第一次获取connection时,
都会往自己的属性threadLocals 中存一份自己的connection,往后直接用就可以了
*/
public class TopicDao { //使用ThreadLocal保存Connection变量
private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>(); public static Connection getConnection(){
//如果connThreadLocal没有本线程对应的Connection则创建一个新的Connection,并将其作为value保存到本线程的ThreadLocalMap中
if (connThreadLocal.get() == nu1l) {
Connection conn = ConnectionManager.getConnection();
connThreadLocal.set(conn);
return conn;
}else{
//直接返回当前线程的value
return connThreadLocal.get(); }
public void addTopic() {
//从ThreadLocal中获取当前线程对应的数据库连接
Statement stat=getConnection().createStatement();
}

ThreadLocal产生的问题

内存泄漏、造成脏数据

(1)内存泄漏

ThreadLocalMap也是一个map,它的key为"公用ThreadLocal变量",value为想要保存的值,value会被保存在ThreadLocalMap的一个数组属性【private Entry[] table】中,而Entry继承WeakReference是弱引用,弱引用的对象在下次GC时会被回收。假如table[0]已经保存了某个Entry对象,由上面“用ThreadLocal存东西”可知Entry对象中有两个属性【value和“公用ThreadLocal变量”】,它们都为强引用,当该table下标位置被重新赋值为另外一个Entry对象时,原Entry对象就失去外部的强引用指针,下次gc会回收该Entry对象,但是该Entry对象里面的两个强引用属性【value和“公用ThreadLocal变量”】则不会被回收,导致内存泄漏,解决:每个线程使用完ThreadLocal之后,显式调用ThreadLocal的remove(),它会帮忙释放两个强引用属性的内存(直接置为null)

public void remove() {
//获取当前线程
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//this=公用的ThreadLocal变量
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
//利用"公用的ThreadLocal变量"计算下标
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
//移除两个强引用属性中的【“公用ThreadLocal变量”】
e.clear();
//移除两个强引用属性中的【value】
expungeStaleEntry(i);
return;
}
}
}

(2)脏数据:线程池会复用Thread对象,故而Thread对象中的static静态属性ThreadLocal也会被复用,所以需要显示调用ThreadLocal的remove()清理掉上一个线程的信息,否则若下一个复用的线程若不调用set()设置该线程初始值,而直接调用get()就会获取到它前一个线程设置的值

OK,如果文章哪里有错误或不足,欢迎各位留言。

创作不易,各位的「三连」是二少创作的最大动力!我们下期见!

ThreadLocal的原理及产生的问题的更多相关文章

  1. 线程局部变量ThreadLocal的原理及使用范围_1

    线程局部变量ThreadLocal的原理及使用范围 使用原理 每个Thread中都有一个ThreadLocalMap成员, 该成员是ThreadLocal的内部类ThreadLocalMap类型.每使 ...

  2. ThreadLocal的原理和在框架中的应用

    ThreadLocal的原理和在框架中的应用 博客分类: java基础 框架多线程SpringthreadDAO  概述      我们知道Spring通过各种DAO模板类降低了开发者使用各种数据持久 ...

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

    1.大概去哪里看 ThreadLocal 其根本实现方法,是在Thread里面,有一个ThreadLocal.ThreadLocalMap属性 ThreadLocal.ThreadLocalMap t ...

  4. ThreadLocal工作原理

    原文出处: imzoer 在这篇文章中,总结了一下面试过程中遇到的关于ThreadLocal的内容.总体上说,这样回答,面试算是过得去了.但是,这样的回答,明显仅仅是背会了答案,而没有去研究Threa ...

  5. ThreadLocal的原理,源码深度分析及使用

    文章简介 ThreadLocal应该都比较熟悉,这篇文章会基于ThreadLocal的应用以及实现原理做一个全面的分析 内容导航 什么是ThreadLocal ThreadLocal的使用 分析Thr ...

  6. 对ThreadLocal实现原理的一点思考

    前言 在<透彻理解Spring事务设计思想之手写实现>中,已经向大家揭示了Spring就是利用ThreadLocal来实现一个线程中的Connection是同一个,从而保证了事务.本篇博客 ...

  7. 【原理】Java的ThreadLocal实现原理浅读

    当前线程的值传递,ThreadLocal 通过ThreadLocal设值,在线程内可获取,即时获取值时在其它Class或其它Method. public class BasicUsage { priv ...

  8. ThreadLocal实现原理

      一.ThreadLocal介绍     这是一个线程的局部变量.也就是说,只有当前线程可以访问.既然是只有当前线程可以访问的数据,自然是线程安全的.     为每一个线程分配不同的对象,需要在应用 ...

  9. ThreadLocal使用原理、注意问题、使用场景

    想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadLocal的使用方法和实现原理.首先,本文先谈一下对ThreadLocal的理解,然后根据ThreadLocal类的源码 ...

随机推荐

  1. path()的name属性,有什么用?

    官网(参考:命名 URL 模式) 命名 URL 模式: 为了完成反向解析 URL ,你需要像上面那样使用 命名 URL 模式 .用于命名 URL 的字符串可以包含任意字符,并不仅限于 Python 里 ...

  2. 半吊子菜鸟学Web开发 -- PHP学习 4 --异常

    PHP异常处理 1 抛出一个异常 与Python的try except类似,PHP用try catch来捕获异常 基本语法 try{ //可能出现错误或异常的代码 //catch表示捕获,Except ...

  3. spring cloud 的核心组件有哪些?

    Eureka:服务注册于发现. Feign:基于动态代理机制,根据注解和选择的机器,拼接请求 url 地址,发起请求. Ribbon:实现负载均衡,从一个服务的多台机器中选择一台. Hystrix:提 ...

  4. Java的HashMap是如何工作的?

    HashMap是一个针对数据结构的键值,每个键都会有相应的值,关键是识别这样的值. HashMap 基于 hashing 原理,我们通过 put ()和 get ()方法储存和获取对象.当我们将键值对 ...

  5. Dubbo telnet 命令能做什么?

    dubbo 服务发布之后,我们可以利用 telnet 命令进行调试.管理. Dubbo2.0.5 以上版本服务提供端口支持 telnet 命令 连接服务 telnet localhost 20880 ...

  6. Spring-MVC 里面的ModelAndView

    作用: 由于本身http是无状态的并不会保存什么请求信息.  但是目标页面可能又需要一些信息.这时候可以用ModelAndView存放一些业务数据之类等.然后返回给页面 用法: 比较重要的方法: 往对 ...

  7. 在虚拟机里面运行java程序

    首先输入vi在里面写一个java程序 然后再查找jdk 复制jdk名字 然后安装jdk 安装完之后输入Javac加你创建的文件名 然后再输入Java 和文件名(这个不要加后缀)然后就打印出来了

  8. SpringDataJpa备忘录

    单向多对一关系 //产品类型 一的一方 @Entity public class ProductDir { @Id @GeneratedValue private Long id; private S ...

  9. Netty学习摘记 —— Netty客户端 / 服务端概览

    本文参考 本篇文章是对<Netty In Action>一书第二章"你的第一款 Netty 应用程序"的学习摘记,主要内容为编写 Echo 服务器和客户端 第一款应用程 ...

  10. 记MyBaits-Plus 实现菜单的无限层关系

    Mybatis-Plus父子菜单 首先来看一下实现的效果 pojo层 @Data @TableName("platform_role") public class Role imp ...