最早接触到ThreadLocal是在阅读dianping的Cat-client,当时对它不是很理解,就搜索了一下,大概了解是一种解决线程安全问题的机制。现在再次阅读《实战java高并发程序设计》时,又重新对它有了更深一步的了解。

并发程序很重要的主题就是解决多线程安全的问题,最常见的处理办法就是引入锁的机制;但是锁使得各个线程对临界区的使用效率变差,于是有了一种新的思路,即每个线程独立管理某个变量,变量的修改在线程中时独立的。就好比,以前锁的机制是100个人签到,只有一个签字薄;而现在ThreadLocal是每个人一张纸。

不过上面的场景,只是threadLocal的一个应用场景。还有个例子,是在城市里面倒车。小明去上班要先做公交车在做地铁,如果每次坐车都买票,那么时间效率很差。于是小明办理了一张通用的公交卡,公交车和地铁都可以刷。而小蓝小红也有这样的公交卡,它们的公交卡彼此之间是独立的。这就是ThreadLocal的作用!

所以说,ThreadLocal并不是解决线程共享问题,而是为了解决单个线程内部变量的独立性和参数传递的问题。

那么它的原理时什么样的呢?

说白了,就是每个线程自己有一个Map,这个Map采用了线性探测法来存储变量。接下来主要阅读下代码吧:

public T get() {
Thread t = Thread.currentThread();
// 获取当前线程的localmap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 用当前的变量作为key查询对象
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果不存在的话,初始化变量
return setInitialValue();
} public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

主要时那个getEntry方法:

private Entry getEntry(ThreadLocal<?> key) {
// 通过当前key的hashcode与列表的长度做 &操作,判断存储的位置
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
// 如果不存在的话,进入getEntryAfterMiss方法
// 这种情况,可能是key被回收掉了;也可能是hash冲突了
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;
}

需要注意的是,ThreadLocal里面的内存结构是这样的:



由于key是弱引用,因此在gc的时候会被回收掉。所以entry中会包含key为null的值,那么这里会不会有内存泄漏呢?可以看一下expungeStaleEntry方法,在发现有value为null的时候,threadlocal会自动扫描其他的元素,看看有没有key为null的,如果有的话,一并移除。

如果为null,需要清理对应的value:

private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length; // expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--; // Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null; // Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}

官方推荐使用private static修饰,跟Java大神聊了下,总结一下ThreadLocal为什么推荐这样使用:

  1. 推荐用private修饰,是不向外部其他的对象也能引用到,防止干扰垃圾回收
  2. 推荐使用static,我个人的理解是为了把对象存储到方法去(static修饰的变量会存储在方法区),这样虽然内部的Entry是弱引用,但由于变量在方法区,也不会在gc的时候被回收掉。<---个人理解哈,如有不对,还请指正

应用

在cat的代码中:

public class DefaultMessageManager extends ContainerHolder implements MessageManager, Initializable, LogEnabled {
// we don't use static modifier since MessageManager is configured as singleton
private ThreadLocal<Context> m_context = new ThreadLocal<Context>(); // 每个线程拥有独立的上下文信息
private Context getContext() {
if (Cat.isInitialized()) {
Context ctx = m_context.get(); if (ctx != null) {
return ctx;
} else {
if (m_domain != null) {
ctx = new Context(m_domain.getId(), m_hostName, m_domain.getIp());
} else {
ctx = new Context("Unknown", m_hostName, "");
} m_context.set(ctx);
return ctx;
}
} return null;
} @Override
public void end(Transaction transaction) {
Context ctx = getContext(); if (ctx != null && transaction.isStandalone()) {
if (ctx.end(this, transaction)) {
m_context.remove();
}
}
}

ThreadLocal从源码到应用的更多相关文章

  1. 对ThreadLocal的源码解读

    早在JDK 1.2的版本中就提供Java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路.使用这个工具类可以很简洁地编写出优美的多线程程序. 功能 ...

  2. 并发编程(四):ThreadLocal从源码分析总结到内存泄漏

    一.目录      1.ThreadLocal是什么?有什么用?      2.ThreadLocal源码简要总结?      3.ThreadLocal为什么会导致内存泄漏? 二.ThreadLoc ...

  3. ThreadLocal终极源码剖析

    目录一.ThreadLocal1.1 源码注释1.2 源码剖析      散列算法-魔数0x61c88647      set操作    get操作    remove操作1.3 功能测试1.4 应用 ...

  4. ThreadLocal终极源码剖析-一篇足矣!

    本文较深入的分析了ThreadLocal和InheritableThreadLocal,从4个方向去分析:源码注释.源码剖析.功能测试.应用场景. 一.ThreadLocal 我们使用ThreadLo ...

  5. ThreadLocal 从源码角度简单分析

    目录 ThreadLcoal源码浅析 ThreadLocal的垃圾回收 Java引用 ThreadLocal的回收 各线程中threadLocalMap的回收 内存泄露问题 总结 参考 ThreadL ...

  6. ThreadLocal<T> 源码解析

    在activeJDBC框架内部的实现中看到了 ThreadLocal 这个类,记录下了每个线程独有的连接 private static final ThreadLocal<HashMap< ...

  7. ThreadLocal部分源码分析

    结构演进 早起JDK版本中,ThreadLocal内部结构是一个Map,线程为key,线程在"线程本地变量"中绑定的值为Value.每一个ThreadLocal实例拥有一个Map实 ...

  8. lesson1:threadlocal的使用demo及源码分析

    本文中所使用的demo源码地址:https://github.com/mantuliu/javaAdvance 其中的类Lesson1ThreadLocal 本文为java晋级系列的第一讲,后续会陆续 ...

  9. 结合ThreadLocal来看spring事务源码,感受下清泉般的洗涤!

    在我的博客spring事务源码解析中,提到了一个很关键的点:将connection绑定到当前线程来保证这个线程中的数据库操作用的是同一个connection.但是没有细致的讲到如何绑定,以及为什么这么 ...

随机推荐

  1. html5 实现 文件夹上传

    先插个背景:最近所在项目有个小需求,就是上传文件要可以同时选择文件夹及文件,然后把文件夹内得文件及所选单文件全部选择上传,借助于搜索关键词没搜到想要的结果(相关文章貌似很好,要么就是遍历文件夹内的文件 ...

  2. 数据结构与算法(C/C++版)【绪论/线性表】

    声明:数据结构与算法系列博文参考了<天勤高分笔记>.<王道复习指导>.C语言中文网.非商业用途,仅为学习笔记总结! 第一章<绪论> 一.基本概念及入门常识  /// ...

  3. Android APP 性能优化的一些思考

    说到 Android 系统手机,大部分人的印象是用了一段时间就变得有点卡顿,有些程序在运行期间莫名其妙的出现崩溃,打开系统文件夹一看,发现多了很多文件,然后用手机管家 APP 不断地进行清理优化 ,才 ...

  4. 深入理解Java虚拟机---学习感悟以及笔记

    一.为什么要学习Java虚拟机?       这里我们使用举例来说明为什么要学习Java虚拟机,其实这个问题就和为什么要学习数据结构和算法是一个道理,工欲善其事,必先利其器.曾经的我经常害怕处理内存溢 ...

  5. PHP 使用redis实现秒杀

    PHP 使用redis实现秒杀 使用redis队列,因为pop操作是原子的,即使有很多用户同时到达,也是依次执行,推荐使用(mysql事务在高并发下性能下降很厉害,文件锁的方式也是) 先将商品库存如队 ...

  6. Expected MultipartHttpServletRequest: is a MultipartResolver configured解决方案

    异常信息: java.lang.IllegalArgumentException: Expected MultipartHttpServletRequest: is a MultipartResolv ...

  7. labview生成可执行文件

    labview生成可执行文件可以分为两种情况. 第一种,是电脑中有labview软件开发环境的情况 第二种,是电脑中没有安装labview软件开发环境 下面是一个简单的labview代码: 程序解释: ...

  8. lodash源码分析之compact中的遍历

    小时候, 乡愁是一枚小小的邮票, 我在这头, 母亲在那头. 长大后,乡愁是一张窄窄的船票, 我在这头, 新娘在那头. 后来啊, 乡愁是一方矮矮的坟墓, 我在外头, 母亲在里头. 而现在, 乡愁是一湾浅 ...

  9. Java集合源代码剖析(二)【HashMap、Hashtable】

    HashMap源代码剖析 ; // 最大容量(必须是2的幂且小于2的30次方.传入容量过大将被这个值替换) static final int MAXIMUM_CAPACITY = 1 << ...

  10. idea svn 设置忽略 文件

    这里的忽略一直灰色的,可以进入 这里的版本控制里进行忽略选择 或者 这里进行添加 这里有三个选择 按照顺序 1.忽略指定的文件 2.忽略文件夹下所有文件 3.忽略符合匹配规则的文件 到Commit C ...