文档章节

浅析ThreadLocal

那只是一股逆流
 那只是一股逆流
发布于 2017/03/02 10:27
字数 1631
阅读 27
收藏 0

对于刚接触多线程的初学者来说,会发现很多框架都出现过ThreadLocal的身影,知道这个类是为了避免在多线程条件下出现资源竞争的问题。但是,在没有深入了解线程之前,更多的是知道ThreadLocal的目的是什么,对于是什么、怎么用、运行原理相信都是一个很模糊的概念,我希望这篇博客能够揭开困扰大家已久的谜团,以及带给大家一些相关知识。

说到要分析东西的时候,我觉得有一个比较好的方法可以帮助大家:不管分析什么,框架也好、源码也好,首先要对这个东西有一个概况了解、知道它是做什么的。千万不要一上来就看的很细很细,这样容易把自己绕晕而且效率很低(血的教训,哭);如果是分析JDK源码,尤其是集合框架的时候,可以先弄懂它的底层数据结构是什么,把底层数据结构弄懂了,那么分析这个类只是水到渠成的事情。哈哈

实现原理

其实就像我上面所说的那样,只要对线程有了一个深入的了解认识以后,就会很容易的理解ThreadLocal。

我们先用通俗的话解释一下ThreadLocal是一个什么东西,一句话:通过ThreadLocal保存的对象将在每一个使用它的线程留存一个副本,从而避免资源竞争,以空间换时间。

我们很自然的会有这样的疑问:

  1. ThreadLocal是如何将对象保存在各个线程中的呢?
  2. ThreadLocal又是如何获取保存在线程本地的对象的呢?

其实,我们可以这样猜:Thread应该有一个Map,保存了ThreadLocal维护的对象。 事实上ThreadLcoal和Thread也是这么做的:

  1. Thread类持有了ThreadLcoal类中有一个内部类ThreadLocalMap,通过将对象保存在这个Map中来实现每个线程都持有一个对象副本;
  2. 以ThreadLocal为Key,进行Key/Value存取操作。

ThreadLocal.java

//我们可以清晰看到get方法的逻辑:1、通过当前线程获取ThreadLocalMap;2、通过this(ThreadLocal)获取线程本地对象
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();
}
//set方法的逻辑很简单,如果map存在则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);
}

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;
        }
    
    }
}

Thread类中则持有了这个map, Thread.java

ThreadLocal.ThreadLocalMap threadLocals = null;

如何工作

我们最关心的还是如何使用ThreadLocal,与使用息息相关的就是set()和get()方法,我们把上面的代码拿过来再用一下:


public T get() {
    //获取当前线程,用于获取ThreadLocalMap
    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;
        }
    }
    //如果为map == null,则执行setInitialValue()方法,进行初始化
    return setInitialValue();
}

//设置初始值,逻辑与get()方法类似
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;
}

//初始化副本,注意:这是一个protected方法,子类必须实现这个方法以生成副本
protected T initialValue() {
    return null;
}

//set方法的逻辑很简单,如果map存在则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);
}

代码中的注释应该比较详细,这里就不写了~ 如果有不懂的,欢迎留言~

WeakReference

WeakReference是干啥的

细心的同学肯定发现了ThreadLocalMap的内部类Entry继承了WeakReference:

static class Entry extends WeakReference<ThreadLocal<?>>

免不了想问WeakReference是干啥的? 我们可以很容易联想到Java中的四种引用关系,而WeakReference对应的是弱引用关系; 我们来看WeakReference的doc中第一句话,

Weak reference objects, which do not prevent their referents from being
 made finalizable, finalized, and then reclaimed. 
 
 弱引用不会阻止它引用的对象变成finalizable、finalized状态并最终被回收

那,Entry为什么要继承WeakReference呢?

Entry为什么要继承WeakReference

如果线程太多(如线程池中的线程),那么保存的副本将会很多,而且不能回收。 因为thread没有运行结束那么会一直存在不能被回收,而threadLocals与对象副本之间是强引用的关系,所以这些副本也就不会被回收。大多数thread都是在阻塞等待状态,真正运行的thread只是少数,所以这些阻塞等待状态的thread所保存的副本就白白浪费了宝贵的内存资源。为了解决这个问题,JDK让Entry类继承了WeakReference类,这样当JVM进行GC的时候可以回收Thread保存的副本。这就意味着,副本对象必须是易于创建的,状态简单的对象,如数据库连接Connection。

Java中的四种引用

  1. 强引用 只要强引用的存在,垃圾回收器永远不会回收这个对象;
  2. 软引用(SoftReference) 当内存不足时,垃圾回收器将会回收被软引用引用的对象;
  3. 弱引用(WeakReference) 垃圾回收器将会在下一次GC时回收被弱引用引用的对象
  4. 虚引用(PhantomReference) 虚引用的存在与否完全不会影响垃圾回收器回收这个对象,虚引用存在的意义是对象被回收时发出一个通知。

扩展阅读

参考资料

© 著作权归作者所有

共有 人打赏支持
那只是一股逆流
粉丝 9
博文 22
码字总数 26214
作品 0
南岸
后端工程师
私信 提问
我对java String的理解 及 源码浅析

摘要: 摘要: 原创出处: http://www.cnblogs.com/Alandre/ 泥沙砖瓦浆木匠 希望转载,保留摘要,谢谢! 每天起床告诉自己,自己的目标是 ”技术 + 英语 还有生活“! -泥沙砖瓦浆木匠 一.char...

泥沙砖瓦浆木匠
2014/08/17
0
0
sharding-jdbc分库分表规则(1)-单表查询

前言 当数据量到达一定数量级的时候,一般都会考虑分库分表。sharding-jdbc是一个开源的客户端分库分表基础类库,以一个jar包的形式提供,基于原生的JDBC驱动进行增强,基本能够无缝整合旧代...

xiaomin0322
06/07
0
0
Java web热部署 (Javarebel实现真正高效的tomcat热部署)

Java web开发部署效率浅析 在进行java web程序开发过程中,经常遇到这种问题,修改一个java文件(*.java),需要重启web服务器(如tomcat,weblogic等),部署项目。而起/停服务器浪费了大量的...

coolcooldee
2013/06/04
5.8K
8
java.io.Serializable浅析

 Java API中java.io.Serializable接口源码: 1 public interface Serializable {2 }   类通过实现java.io.Serializable接口可以启用其序列化功能。未实现次接口的类无法使其任何状态序列化...

偶尔诗文
2015/08/16
0
0
MySQL JDBC StreamResult通信原理浅析

使用MySQL JDBC读取过较大数据量的人应该清楚(例如超过1GB),在读取的时候内存很可能会Java堆内存溢出,而我们的解决方案是statement.setFetchSize(Integer.MIN_VALUE)并确保游标是只读向前...

阿里云云栖社区
10/18
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Spring源码学习笔记-1-Resource

打算补下基础,学习下Spring源码,参考书籍是《Spring源码深度解析》,使用版本是Spring 3.2.x,本来想试图用脑图记录的,发现代码部分不好贴,还是作罢,这里只大略记录下想法,不写太细了 ...

zypy333
今天
10
0
RestClientUtil和ConfigRestClientUtil区别说明

RestClientUtil directly executes the DSL defined in the code. ConfigRestClientUtil gets the DSL defined in the configuration file by the DSL name and executes it. RestClientUtil......

bboss
今天
17
0

中国龙-扬科
昨天
2
0
Linux系统设置全局的默认网络代理

更改全局配置文件/etc/profile all_proxy="all_proxy=socks://rahowviahva.ml:80/"ftp_proxy="ftp_proxy=http://rahowviahva.ml:80/"http_proxy="http_proxy=http://rahowviahva.ml:80/"......

临江仙卜算子
昨天
11
0
java框架学习日志-6(bean作用域和自动装配)

本章补充bean的作用域和自动装配 bean作用域 之前提到可以用scope来设置单例模式 <bean id="type" class="cn.dota2.tpye.Type" scope="singleton"></bean> 除此之外还有几种用法 singleton:......

白话
昨天
10
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部