ThreadLocal

先来看看ThreadLocal的注释:

This class provides** thread-local variables**. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

翻译过来就是:ThreadLocal提供了线程级的变量,这个变量和其他的使用set、get访问的变量不一样,ThreadLocal针对每个线程会为该变量维护经过单独初始化的副本。ThreadLocal实例希望定义为private static。

从注释可以看出ThreadLocal解决的问题是:

  • 单线程的变量共享

单线程变量共享

如果是变量共享为什么不用一个全局变量就好呢?主要是因为ThreadLocal为每个线程维护经过单独初始化的变量副本,但是普通的变量访问到的就是同一个。

上面的注释也说了,ThreadLocal会为每个线程维护经过单独初始化的变量副本,每个线程访问的都是自己的副本,所以各个线程之间不会相互影响,达到隔离的作用

线程同步解决的是多线程访问共享资源的问题,但是ThreadLocal本身并不是用来多线程之间共享的,只是用来单线程共享的,所以ThreadLocal和线程同步根本不是一回事儿

实现原理

  • 每个Thread都有一个ThreadLocal.ThreadLocalMap,因为Thread类里面有一个该类的对象,用来存放该线程中所有的ThreadLocal类型的变量

  • ThreadLocalMap里面有一个Entry(继承自WeakReference,一个对象保存一个键值对)数组,根据key(这里就是ThreadLocal变量本身)的哈希值将value(这里就是需要保存的数据)散列到数组中

  • 初次调用threadLocal.get的时候如果ThreadLocalMap尚未初始化,会调用createMap初始化

ThreadLocalMap初始化

public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
// 如果map未初始化
return setInitialValue();
} 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;
} void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
} ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
// 新建初始大小为16的数组
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
// 设置扩容的阈值
setThreshold(INITIAL_CAPACITY);
}

get

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 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)
// 如果key为null则进行一次清除
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}

set

/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
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); // 使用的是开放地址法解决冲突,如果发生碰撞则向后查找
// 如果得到的i位置已经有值,那么就向后一个单位尝试填充
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
// 如果是相同的key就替换,说明同一个对象中的
// 同一个threadLocal变量
if (k == key) {
e.value = value;
return;
}
// 因为是弱引用,ThreadLocal已经被回收,所以key就是null,将value放在这个位置
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
} // 直到找到一个元素为空的位置(e == null),
// 每新占用一个数组位置(上面都是在替换原来元素或者替换
// 已经被移除的元素,size已经加过的)就要判断是否需要进行扩容
tab[i] = new Entry(key, value);
int sz = ++size;
// 如果没有清除数组中的元素并且元素个数已经大于等于阈值threshold则进行扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
} // 扫描数组清除陈旧的数据,但并不是全部扫描,而是log2(n)对数扫描
// 在全部扫描和不扫描之间取一个折中
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length; do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
// 无符号右移一位,相当于每次除2取整
} while ( (n >>>= 1) != 0);
return removed;
} // 在staleSlot到下一个数组元素为null之间,清空陈旧(key为null)的元素,
// 重新散列非陈旧的元素
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 void remove(ThreadLocal key) {
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)]) {
if (e.get() == key) {
e.clear();
// 将若引用置为null之后,进行一次清除
expungeStaleEntry(i);
return;
}
}
}

Java 中的引用

各种不同的引用的区别就是引用到的对象被垃圾回收的时机不同

  • 强引用:如果有强引用到一个对象,那么该对象不会被回收
  • 弱引用:只有弱引用链接的对象,在系统进行GC的时候就会被垃圾回收
  • 软引用:只有内存不够的时候,在会被GC回收
  • 虚引用:任何时候都可以被回收

神奇的0x61c88647

在ThreadLocalMap的hash算法中,很少发生碰撞,原因在于精巧的hash算法

private final int threadLocalHashCode = nextHashCode();

private static final int HASH_INCREMENT = 0x61c88647;

private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
} 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);
}

这里最不解的是为什么要用0x61c88647这个数?怎么来的?

0x61c88647换算成十进制是1640531527,计算方法如下

1640531527 = (long) ((1L << 31) * (Math.sqrt(5) - 1))

(Math.sqrt(5) - 1) / 2 是黄金分割数

这种hash方法是Donald Knuth在 The Art of Computer Programming 中提出,不明觉厉

问题

清除stale数组元素的标准就是 key == null,什么时候ThreadLocal.key为null?

key为null:因为key是ThreadLocal的弱引用,当ThreadLocal没有强引用的时候,ThreadLocal变量可能会被回收,这个时候出现了key为null的情况

Java 线程 — ThreadLocal的更多相关文章

  1. Java线程与并发库高级应用-线程范围内共享数据ThreadLocal类

    1.线程范围内共享变量 1.1 前奏: 使用一个Map来实现线程范围内共享变量 public class ThreadScopeShareData { static Map<Thread, In ...

  2. java面试题之什么是线程ThreadLocal?作用是什么?

    定义:线程局部变量是局限于线程内的变量,属于线程自身所有,不在多个线程间共享.java提供ThreadLocal类来支持线程局部变量,是一个实现线程安全的方式. 任何线程局部变量一旦在工作完成后没有释 ...

  3. Java线程变量问题-ThreadLocal

    关于Java线程问题,在博客上看到一篇文章挺好的: https://blog.csdn.net/w172087242/article/details/83375022#23_ThreadLocal_1 ...

  4. Java线程并发:知识点

    Java线程并发:知识点   发布:一个对象是使它能够被当前范围之外的代码所引用: 常见形式:将对象的的引用存储到公共静态域:非私有方法中返回引用:发布内部类实例,包含引用.   逃逸:在对象尚未准备 ...

  5. Java线程的概念

    1.      计算机系统 使用高速缓存来作为内存与处理器之间的缓冲,将运算需要用到的数据复制到缓存中,让计算能快速进行:当运算结束后再从缓存同步回内存之中,这样处理器就无需等待缓慢的内存读写了. 缓 ...

  6. 【转载】 Java线程面试题 Top 50

    Java线程面试题 Top 50 不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题.Java语言一个重要的特点就是内置了对并发的支持,让Java大受企业和程序员 的欢迎.大多数待遇丰厚的J ...

  7. java线程内存模型,线程、工作内存、主内存

    转自:http://rainyear.iteye.com/blog/1734311 java线程内存模型 线程.工作内存.主内存三者之间的交互关系图: key edeas 所有线程共享主内存 每个线程 ...

  8. Java线程的5个使用技巧

    萝卜白菜各有所爱.像小编我就喜欢Java.学无止境,这也是我喜欢它的一个原因.日常工作中你所用到的工具,通常都有些你从来没有了解过的东西,比方说某个方法或者是一些有趣的用法.比如说线程.没错,就是线程 ...

  9. 【Java】ThreadLocal细节分析

    ThreadLocal通过中文解释就是线程本地变量,是线程的一个局部变量.根据哲学家黑格尔“的存在即合理”的说法,ThreadLocal的出现肯定是有它的意义,它的出现也是因为多线程的一个产物.Thr ...

随机推荐

  1. ArcGIS API中FindTask中文搜索无效,服务器编码问题URIEncoding="utf-8"

    问题来源:字符编码问题导致ArcMap中字符乱码或显示不正常,因而在F:\Program Files\ArcGIS\Server\framework\runtime\tomcat\conf中serve ...

  2. 核心动画(CAKeyframeAnimation)

    Main.storyboard ViewController.m // //  ViewController.m //  8A02.核心动画 - CAKeyframeAnimation // //  ...

  3. Linux下TCP网络编程与基于Windows下C#socket编程间通信

    一.linux下TCP网络编程基础,需要了解相关函数 Socket():用于套接字初始化. Bind():将 socket 与本机上的一个端口绑定,就可以在该端口监听服务请求. Listen():使s ...

  4. React Native填坑之旅--组件生命周期

    这次我们来填React Native生命周期的坑.这一点非常重要,需要有一个清晰的认识.如果你了解Android或者iOS的话,你会非常熟悉我们今天要说的的内容. 基本上一个React Native的 ...

  5. LightOJ Beginners Problems 部分题解

    相关代码请戳 https://coding.net/u/tiny656/p/LightOJ/git 1006 Hex-a-bonacci. 用数组模拟记录结果,注意取模 1008 Fibsieve's ...

  6. zmq中zmq_poll()函数介绍

    功能: 查看指定的多个socket上哪些socket发生了指定的事件, 事件有: ZMQ_POLLIN: 有消息到来 ZMQ_POLLOUT: 当前无阻塞可以发送消息 ZMQ_POLLERR: 只对标 ...

  7. 8.3 网络通信 Volley

    AsyncHttpClient,它把HTTP所有的通信细节全部封装在了内部,我们只需要简单调用几行代码就可以完成通信操作了. Universal-Image-Loader,它使得在界面上显示网络图片的 ...

  8. html5+css3 制作音乐播放器

    //css// body , html{    margin:0;    padding:0;    font:12px Arial, Helvetica, sans-serif;    } .Mus ...

  9. 网页播放器(jsp、js)

    jsp对控件显示 <%@ page language="java" import="java.util.*" pageEncoding="UTF ...

  10. Canny边缘检测及图像缩放之图像处理算法-OpenCV应用学习笔记四

    在边缘检测算法中Canny颇为经典,我们就来做一下测试,并且顺便实现图像的尺寸放缩. 实现功能: 直接执行程序得到结果如下:将载入图像显示在窗口in内,同时进行图像两次缩小一半操作将结果显示到i1,i ...