目录
ThreadLocal
此类提供线程局部变量。这些变量不同于普通的对应变量,因为每个访问一个(通过其get或set方法)的线程都有自己独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态(private static (在java当中可以理解static就是全局的意思))字段,它们希望将状态与线程相关联(例如,用户ID或事务ID)。
本质上,ThreadLocal是通过空间来换取时间,从而实现每个线程当中都会有一个变量的副本,这样每个线程都会操作该副本这样每个线程都会操作该副本,从而完全规避了多线程的并发问题
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0);
// Thread local variable containing each thread's ID
//确保当前类的所有实例都能访问到静态的Thread的变量
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() {
@Override protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
}
ThreadLocalMap
ThreadLocalMap是一个定制的哈希映射,只适合维护线程本地值。在ThreadLocal类之外不导出任何操作(为了辅助ThreadLocal存储数据)。该类是包私有的,以允许在类线程中声明字段。为了帮助处理非常大和长期的使用,哈希表条目使用weakreference作为键。但是,由于不使用引用队列,因此只有当表空间开始不足时,才能保证删除过时的条目。
/**
改静态内部类主要是用来组装和存储当前线程的数据 ThreadLocal 做key 变量做value
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
//与此ThreadLocal关联的值。
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* 初始容量——必须是2的幂次方。
*/
private static final int INITIAL_CAPACITY = 16;
/**
* 底层Entry 数组用来存储 各个线程以及各个线程的变量副本
* 由于每个线程都对应管理的ThreadLocal 和值
* */
private Entry[] table;
/**
* 表中的条目数。
*/
private int size = 0;
/**
* 要调整大小的下一个大小值。
*/
private int threshold; // Default to 0
我们要知道 private Entry[] table;该成员变量 存储各个线程的Entry对象 即 线程的ThreadLocal 和对应副本 的集合数组
示例
/*
ThreadLocal
本质上,ThreadLocal是通过空间来换取时间,从而实现每个线程当中都会有一个变量的副本,这样每个线程都会操作该副本
这样每个线程都会操作该副本,从而完全规避了多线程的并发问题
Thread 和ThreadLocal 是通过 ThreadLocalMap 来进行关联交互的
java中存在四种类型的引用:
1. 强引用(strong) 对象被强引用 garbage collector是如论如何都不会回收该对象
2. 软引用(soft) 可用内存不足 才会回收 前提强引用不指向该对象
3. 弱引用(weak) 下一次scavenge GC 对象才会被回收 前提强引用不指向该对象
4. 虚引用(phantom) 当GC时 虚引用的对象 后收到通知 准备后续的一些处理
上述除了 强引用时直接new 其他的都是需要继承Reference 并将一些方法实现
*/
public class MyTest3 {
public static void main(String[] args) {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set(" hello world");
System.out.println(threadLocal.get());
}
}
ThreadLocal.set
ThreadLocal的使用非常简单我们来看ThreadLocal的Set方法
/**
将此线程局部变量的当前线程副本设置为指定值。大多数子类不需要重写这个方法,
只依赖initialValue方法来设置线程局部变量的值。
参数:
value–要存储在此线程本地的当前线程副本中的值。
*/
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的对应的ThreadLocalMap 副本数据
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else //没有就创建
createMap(t, value);
}
ThreadLocalMap.getMap
/**
*获取与ThreadLocal关联的映射。
* 在InheritableThreadLocal中重写。
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
在这我们可以看到每个线程对应都有一个ThreadLocalMap 副本数据 由此可以看出每个线程都是互相隔离的并不存在并发问题
/**
创建与ThreadLocal关联的映射。在InheritableThreadLocal中重写。
参数:
t–当前线程
firstValue–地图初始条目的值
*/
void createMap(Thread t, T firstValue) {
//
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
由ThreadLocal.createMap 该方法我们可以看出 。Thread 和ThreadLocal 是通过 ThreadLocalMap 来进行关联交互的将当前存储了ThreadLocalMap放置到当前线程对象当中。
ThreadLocal.get
/**
返回此线程局部变量的当前线程副本中的值。如果变量对于当前线程没有值,
则首先将其初始化为调用initialValue方法返回的值。
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
//获取ThreadLocalMap
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;
}
}
//返回 初始值null
return setInitialValue();
}
ThreadLocalMap.getEntry
private Entry getEntry(ThreadLocal<?> key) {
//threadLocalHashCode 和存储 副本数组的索引 来推断出当前线程的下标
int i = key.threadLocalHashCode & (table.length - 1);
//拿到当前线程的 Entry 数据
Entry e = table[i];
if (e != null && e.get() == key)
return e; //返回当前线程对应的副本数据
else
//找不到使用getEntryAfterMiss 方法 还找不到就返回null
return getEntryAfterMiss(key, i, e);
}
由上我们可以大致知道 ThreadLocal的布局
由图我们可以看出来 Entry引用当前线程的ThreadLocal ,他们之间的关系是弱引用的关系
**1、为什么 Entry extends 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;
}
}
假设Entry引用当前线程的ThreadLocal是强引用关系
当我们当前线程在方法中销毁对ThreadLocal的引用,本应该销毁当前线程的ThreadLocal然而由于 Entry数组中始终对当前线程的ThreadLocal牢牢强引用的关系;GC无法销毁当前的ThreadLocal 此时就会导致Entry中的key value对 永远无法释放 导致内存泄,当我们使用弱引用时下一次scavenge GC 对象就会被回收。
此时又会存在一个问题由于弱引用的关系当K被GC回收变成null 此时V还存在就会导致当前V始终存在并且无法被获取,此时该Entry[] 数组就存在很对key为null 的键值对 此时还是会造成内存泄露;ThreadLocal如果解决的? 答案是当ThreadLocal在get,set和remove的时候会去检查Entry数组中是否有ley为null的键值对 如果有就会删除。
ThreadLocal使用规范
我们在日常开发的使用规范应该遵循如下:
public class Test{
private static final ThreadLocal<String> t1 = new ThreadLocal();
public void Test(){
try{
.....
.....
}finally{
/*
删除对t1堆的引用和清除ThreadLocalMap中Entry[] 数组中 key为null的键值对。
如果不调用极有可能造成内存泄漏 重点
*/
t1.remove();
}
}
}