- 概述
- 与synchronized的区别
- 用法
- 源码分析
- Thread类:ThreadLocalMap threadLocals属性
- ThreadLocalMap类:ThreadLocal的静态内部类,实际是Entry<ThreadLocal, Object>数组
- Entry类:ThreadLocalMap的静态内部类,继承弱引用
- ThreadLocal常用方法
- get(),set()
- 延迟构造性质
- 内存泄漏
- 为什么Entry要继承弱引用
- 内存泄漏原因及避免方法
- InheritabIeThreadLocal
1. 概述
- 介绍
- 官方版:ThreadLocal提供了线程本地变量,它可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同。ThreadLocal相当于提供了一种线程隔离,将变量与线程相绑定
- 通俗版:一个ThreadLocal在一个线程中是共享的,在不同线程之间又是隔离的(每个线程都只能看到自己线程的值)
- ThreadLocal和synchronized的区别:虽然两者都是处理多线程并发访问量的问题,但两者处理问题的角度和思路不同
synchronized | threadLocal | |
原理 | 时间换空间;让不同线程排队访问 | 空间换时间;为每个线程都提供了变量的副本,互不干扰 |
侧重点 | 多线程之间访问资源的同步 | 多线程间数据相互隔离 |
2. 用法
- 创建一个ThreadLocal对象,然后再不同线程中使用get()和set()方法获取该共享变量。
public class Test{
static ThreadLocal<String> tl = new ThreadLocal<>();
public static void main(String[] args){
Thread one = new Thread(new Runnable(){
tl.set("111");
});
Thread two= new Thread(new Runnable(){
tl.set("222");
});
one.start();
two.start();
}
}
3. 源码分析
- Thread类:每个Thread对象里有一个ThreadLocalMap属性,属性名叫threadLocals
public class Thread implements Runnable {
...
ThreadLocalMap threadLocals;
ThreadLocalMap inheritableThreadLocals;
...
}
- ThreadLocalMap类:它是ThreadLocal类中的静态内部类
public class ThreadLocal<T> {
...
static class ThreadLocalMap {
private static final int INITIAL_CAPACITY = 16;
private ThreadLocal.ThreadLocalMap.Entry[] table; //重要属性
private int size = 0;
private int threshold;
...
}
}
- Entry类:ThreadLocalMap中维护了一个Entry[]的数组(优点类似Hashmap的底层结构),其中Entry类又是ThreadLocalMap中自定义的静态内部类,key是ThreadLocal类,value是obj类
- 需要注意的是:这个对象继承了 弱引用,涉及到后面的内存泄露问题
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
this.value = v;
}
}
}
- 总结
- 每个线程(Thread)中都维护了一个ThreadLocalMap的表,实际是一个Entry<ThreadLocal, Object>类的数组
- eg. 假设共享资源为ThreadLocal<String> threadlocal,在线程t1中调用threadlocal.set("123456")方法时,会先得到t1线程,然后在t1线程中的ThreadLocalMap中添加<key = threadlocal, “123456”>的Entry
4. ThreadLocal类常用方法
(1)set()方法
- 1. 先获取当前线程t
- 2. 用ThreadLocal中的getMap方法获取线程t中的ThreadLocalMap对象
- 3. 如果ThreadLocalMap没被初始化过,进入5初始化;如果已经被初始化过,进入4添加该threadlocal
- 4. 调用ThreadLocalMap的set()方法添加(该方法类似hashmap中的添加方法)
- 5. 调用ThreadLocal中的createMap()方法,新建一个ThreadLocalMap并添加该数据和value
(2)get()方法
- 1. 先获取当前线程t
- 2. 用ThreadLocal中的getMap方法获取线程t中的ThreadLocalMap对象
- 情况1:map已被初始化且该entry<threadlocal, value>存在 ——> 返回value
- 情况2:map已被初始化且该entry<threadlocal, value>不存在——>把entry<threadlocal, null>放入map并返回null
- 情况3:map没被初始化过——>初始化,把entry<threadlocal, null>放入map并返回null
(3)ThreadLocalMap延迟构造
- 从上面代码可以看出,只有再第一次调用get()和set()方法时,ThreadLocalMap才会被创建,不使用threadlocal则一直为null
5. 内存泄漏问题
- 现象:Entry对象继承了弱引用(每次gc都会被垃圾回收)
- 为什么要继承弱引用?弱引用与内存泄漏现象有关系吗? ——> 答:没关系
- 如果不继承弱引用,那Entry对象就是一个强引用对象,由于Entry对象组成了threadLocalMap,而后者又与线程的生命周期一样
- 这种情况下,只要线程一直运行,这两个对象都不会被gc ——> 造成内存泄漏
- 继承弱引用:仍然会存在内存泄漏,但是情况比强引用好
- 引起内存泄漏原因:由于ThreadLocal的生命周期和Thread一样长,只要没有手动删除key就会一直存在,导致内存泄漏
- 内存泄漏解决方法
- 使用完ThreadLocal,调用remove方法删除Entry
- 使用完ThreadLocal,当前Thread也随之结束
6. 应用场景
- 经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。
7. InheritabIeThreadLocal
- ThreadLocal存在的问题
- 没有继承性:同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的
- 为了解决上节提出的问题, InheritableThreadLocal 应运而生
- InheritableThreadLocal 继承自 ThreadLocal,其提供了一个特性,就是让子线程可以访问在父线程中设置的本地变量。