首先试想一个场景:

多个线程都要访问数据库,先要获得一个Connection,然后执行一些操作。为了线程安全,如果用synchronized锁定一个Connection对象,那么任何时候,都只有一个线程能通过Connection对象操作数据库。这样的话,程序的效率太低。反过来,如果每次需要Connection对象就去new一个的话,就会同时存在数量庞大的数据库连接,你受得了,数据库受不了。于是就有人提出折中方案:为每个线程只生成一个Connection对象,这样别的线程访问不到这个对象,线程安全问题解决;而且无论线程有多少地方需要数据库连接,都是在复用这个Connection对象,数据库的压力会小很多。

其实不仅仅是数据库,其它的场景比如说,SimpleDateFormat。我们处理日期的时候,经常要用到这个类,但是这个类不是线程安全的,在多线程下是会出问题的。这时候,采用上述折中方案是比较合理的。

那么如何实现这种折中方案呢?我们先动手试一试呗!!!

要确保某类型的变量,每个线程只有一份。因为每个线程的ID是唯一的,这是JVM保证的,所有我们可以定义一个Map:线程ID作为key,我们要用的变量作为value。

稍微对这个Map进行简单的封装,当做一个类来用:

package threadlocal;

import java.util.HashMap;
import java.util.Map; public class ThreadLocalVar<T> { Map<Long, T> threadVarMap = new HashMap<Long, T>(); public T get() {
return threadVarMap.get(Thread.currentThread().getId());
} public void set(T value) {
threadVarMap.put(Thread.currentThread().getId(), value);
}
}

接下来,就把这个类扔到多线程环境里面练一练

package threadlocal;

public class MyTest {
ThreadLocalVar<Long> longLocal = new ThreadLocalVar<Long>();
ThreadLocalVar<String> stringLocal = new ThreadLocalVar<String>(); public void set() {
longLocal.set(Thread.currentThread().getId());
stringLocal.set(Thread.currentThread().getName());
} public long getLong() {
return longLocal.get();
} public String getString() {
return stringLocal.get();
} public static void main(String[] args) throws InterruptedException {
final MyTest test = new MyTest(); test.set();
System.out.println(test.getLong());
System.out.println(test.getString()); for (int i=0; i<3; i++) {
Thread thread1 = new Thread(){
public void run() {
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
};
};
thread1.start();
thread1.join();
} System.out.println(test.getLong());
System.out.println(test.getString());
}
}

这个程序很简单,看一遍就能明白具体逻辑。虽然都是调用的同一个对象test的getLong和getString方法,但是不同的线程获取到的值不一样。

运行结果:

1
main
9
Thread-0
10
Thread-1
11
Thread-2
1
main

哈哈,我们就是使用了奇淫巧技,把一个对象简单的get和set操作,转到了对Map的get和set操作。如果光看MyTest这个类,再看结果,还是挺迷惑的吧。

这个时候就有人说了,Java的ThreadLocal机制,不是这么实现的。对,也不对。JDK之前的老版本其实就是这么实现来着,不过后来改了。为什么改,且听我慢慢道来。

先上一个真正的ThreadLocal版本的test程序:

package threadlocal;

public class Test {
ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
ThreadLocal<String> stringLocal = new ThreadLocal<String>(); public void set() {
longLocal.set(Thread.currentThread().getId());
stringLocal.set(Thread.currentThread().getName());
} public long getLong() {
return longLocal.get();
} public String getString() {
return stringLocal.get();
} public static void main(String[] args) throws InterruptedException {
final Test test = new Test(); test.set();
System.out.println(test.getLong());
System.out.println(test.getString()); for (int i=0; i<3; i++) {
Thread thread1 = new Thread(){
public void run() {
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
};
};
thread1.start();
thread1.join();
} System.out.println(test.getLong());
System.out.println(test.getString());
}
}

和我们之前的test程序唯一的区别,就是使用了Java自带的ThreadLocal类,那就进去看一看。

    /**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
// 其实还是通过Map的数据结构
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();
}

这是ThreadLocal的get方法,最终还是Map操作,但是这个Map以及Map里面的Entry都是为ThreadLocal专门定制的,后面再说。看看getMap方法的逻辑

    /**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
    /* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
// 定义在Thread类里面
ThreadLocal.ThreadLocalMap threadLocals = null;

从这里能看出2点:

1、ThreadLocalMap这个Map是ThreadLocal的内部类

2、这个Map的持有者是Thread类,就是说每个线程都直接持有自己的Map

第2点跟我们之前的实现思路截然不同,我们定义的ThreadLocalVar类不被任何线程直接持有,只是独立的第三方,保持各个线程的数据。

后面再详细分析这里为什么要这么实现。

先来看看ThreadLocal的内部类ThreadLocalMap的内部类Entry(别绕晕了)

        static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value; Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

Entry继承自弱引用,说明持有key的弱引用,而且key是ThreadLocal类型(跟之前的实现方式也截然不同)。

为了说明ThreadLocal的实现机制和类直接的关系,从网上盗一张图,图中实线是强引用,虚线是弱引用。

每个线程持有Map有什么好处?

1、线程消失,Map跟着消失,释放了内存

2、保存数据的Map数量变多了,但是每个Map里面Entry数量变少了。之前的实现里面,每个Map里面的Entry数量是线程的个数,现在是ThreadLocal的个数。熟悉Map数据结构的人都知道,这样对Map的操作性能会提升。

至于为什么要用弱引用,先来看看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.
*/

简单来说,就是当ThreadLocal类型的key不再被引用时(值为null),对应的Entry能够被删除。

具体的实现就是,get操作会调用expungeStaleEntry,set操作会调用replaceStaleEntry,它们的效果就是遇到的key为null的Entry都会被删除,那么Entry内的value也就没有强引用链,自然会被回收,防止内存泄露。这部分,请读者仔细阅读源码。

经这么一分析,是不是豁然开朗。

下面在看看ThreadLocal在一些框架里面的应用:

1、Hibernate处理session,看看一个类ThreadLocalSessionContext

       private static final ThreadLocal<Map> CONTEXT_TL = new ThreadLocal<Map>();

       protected static Map sessionMap() {
return CONTEXT_TL.get();
} @SuppressWarnings({"unchecked"})
private static void doBind(org.hibernate.Session session, SessionFactory factory) {
Map sessionMap = sessionMap();
if ( sessionMap == null ) {
sessionMap = new HashMap();
CONTEXT_TL.set( sessionMap );
}
sessionMap.put( factory, session );
}

2、Spring处理事务,看看一个类TransactionSynchronizationManager

private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<Map<Object, Object>>("Transactional resources"); private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations"); private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<String>("Current transaction name"); private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<Boolean>("Current transaction read-only status"); private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<Integer>("Current transaction isolation level"); private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<Boolean>("Actual transaction active"); public static void bindResource(Object key, Object value) throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Assert.notNull(value, "Value must not be null");
// 处理ThreadLocal
Map<Object, Object> map = resources.get();
// set ThreadLocal Map if none found
if (map == null) {
map = new HashMap<Object, Object>();
// 处理ThreadLocal
resources.set(map);
}
Object oldValue = map.put(actualKey, value);
// Transparently suppress a ResourceHolder that was marked as void...
if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
oldValue = null;
}
if (oldValue != null) {
throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
}
if (logger.isTraceEnabled()) {
logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
Thread.currentThread().getName() + "]");
}
}

ThreadLocal类分析的更多相关文章

  1. ThreadLocal类详解:原理、源码、用法

    以下是本文目录: 1.从数据库连接探究 ThreadLocal 2.剖析 ThreadLocal 源码 3. ThreadLocal 应用场景 4. 通过面试题理解 ThreadLocal 1.从数据 ...

  2. Spring源码分析——BeanFactory体系之抽象类、类分析(二)

    上一篇分析了BeanFactory体系的2个类,SimpleAliasRegistry和DefaultSingletonBeanRegistry——Spring源码分析——BeanFactory体系之 ...

  3. ThreadLocal类的实现用法

    ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量).也许把它命名为Thread ...

  4. ThreadLocal类的简单使用

    1.概述变量值的共享可以使用public 是static 变量的形式,所有的线程都使用同一个public static 变量. 如实现线程内的共享变量,jdk提供了ThreadLocal来解决这个问题 ...

  5. ThreadLocal 类 的源码解析以及使用原理

    1.原理图说明 首先看这一张图,我们可以看出,每一个Thread类中都存在一个属性 ThreadLocalMap 成员,该成员是一个map数据结构,map中是一个Entry的数组,存在entry实体, ...

  6. 「Android」消息驱动Looper和Handler类分析

    Android系统中的消息驱动工作原理: 1.有一个消息队列,可以往这个消息队列中投递消息; 2.有一个消息循环,不断的从消息队列中取得消息,然后处理. 工作流程: 1.事件源将待处理的消息加入到消息 ...

  7. 理解和使用ThreadLocal类

    一.从数据结构入手 下图为ThreadLocal的内部结构图 从上面的机构图,可以窥见ThreadLocal的核心机制: 每个Thread线程内部都有一个Map: Map里面存储线程本地对象(key) ...

  8. 源码分析篇 - Android绘制流程(三)requestLayout()与invalidate()流程及Choroegrapher类分析

    本文主要探讨能够触发performTraversals()执行的invalidate().postInvalidate()和requestLayout()方法的流程.在调用这三个方法到最后执行到per ...

  9. java中ThreadLocal类的使用

    ThreadLocal是解决线程安全问题一个很好的思路,ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本,由于Key值不可重复, ...

随机推荐

  1. AJAX 中JSON 和JSONP 的区别 以及请求原理

    AJAX 跨域请求 - JSONP获取JSON数据 博客分类: Javascript /Jquery / Bootstrap / Web   Asynchronous JavaScript and X ...

  2. webpack2 前篇

    webpack2 前篇 #webpack 前两天用了一天半时间琢磨了下webpack2,想起去年这时候,面对webpack1那样恶心的文档,前前后后搞了好几次才摸索清楚,那真是吐了. 划重点 其实we ...

  3. Python获取股票历史、实时数据与更新到数据库

    要做量化投资,数据是基础,正所谓"巧妇难为无米之炊" 在免费数据方面,各大网站的财经板块其实已提供相应的api,如新浪.雅虎.搜狐...可以通过urlopen相应格式的网址获取数据 ...

  4. easyAR图钉功能实现相关

    图钉功能总算做出来了,发现真的是因为没认真看手册导致的=.=[跪 最后的代码很简单,但是过程中看了不少camera/tracker相关的东西,感觉需要整理记录一下 图钉功能记录: 目标:实现用ARCa ...

  5. Thread(线程)四

    今天继续讲讲线程的异常处理.线程取消.多线程的临时变量和线程安全lock的问题. 1.异步处理. 一般来说如果是同步方法的异步处理,我们大多都是try catch住,但是异步方法应该怎么做呢. #re ...

  6. 微信客户端+微信公众平台+新浪云SAE+Arduino+WS100(控制LED)

    第一步:准备 1.智能手机微信客户端或微信电脑版 2.注册微信公众平台 https://mp.weixin.qq.com 3.注册新浪账号 http://www.sinacloud.com 4.拥有一 ...

  7. mysql5.7.18的安装与主从复制

    CentOS6.7安装mysql5.7.18 1.  解压到/usr/local目录 # tar -zxvf mysql-5.7.18-linux-glibc2.5-i686.tar.gz -C /u ...

  8. HTML5——JSON的方法实现对象copy

    JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式.它基于 ECMAScript (w3c制定的js规范)的一个子集,采用完全独立于编程语言 ...

  9. Java微信开发_02_本地服务器映射外网

    一.工具列表 内网穿透的相关工具有: (1)natapp 官网 :https://natapp.cn/ (2)花生壳 官网:https://console.oray.com/ (2)ngrok 官网: ...

  10. JavaScript 开发总结(一)

    数据类型:JavaScript定义的数据类型有字符串.数字.布尔.数组.对象.Null.Undefined,但typeof有区分可判别的数据分类是number.string.boolean.objec ...