目录
前言
正文
1.方法get() 与 null
2.类ThreadLocal 存取数据流程分析
3.验证线程变量的隔离性
4.解决 get() 返回 null 的问题。
5.验证重写initalValue()方法的隔离性
6.使用remove()方法的必要性
变量值的共享可以使用 public static 变量的形式,所有的线程都使用同一个 public static 变量,那如果想实现每一个线程都有自己的变量该如何解决呢?JDK提供的ThreadLocal就可以派上用场了。
类ThreadLocal主要的作用就是将当数据放入当前线程对象的Map里,这个Map是Thread类的实例变量。类ThreadLocal自己不管理也不存储任何数据,它只是数据和Map之间的中介和桥梁,通过Threadlocal将数据放入Map中,执行流程如下:
数据 -> ThreadLocal -> currentThread() -> Map
执行后每个线程中的 Map 就有存自己的数据,Map 中的 key 存储的是 ThreadLocal 对象,value 就是存储的值,说明ThreadLocal 和 值之间是一对一关系,一个 ThreadLoca 只能关联一个值。每个线程中的 Map 的值只对当前线程可见,其他线程不可以访问当前线程对象中 Map 的值。
线程、Map、值之间的关系可以比喻为:
人(线程)随身有兜子(Map)、兜子(Map)里面有东西(数据),这么实现,线程随身也有自己的数据了,随时可以访问自己的数据了。
由于Map中的key不可以重复,所以一个 ThreadLocal 对象对应一个 value,内存结构如图所示:
如果从未在 Thread 中的 Map 存储 ThreadLocal 对象对应的值,则 get() 方法返回 null。
创建测试代码如下:
public class get_null {
public static ThreadLocal threadLocal = new ThreadLocal();
public static void main(String[] args) {
if (threadLocal.get() == null){
System.out.println("从未放过值");
threadLocal.set("我的值");
}
System.out.println(threadLocal.get());
System.out.println(threadLocal.get());
}
}
程序运行结果如图:
从运行结果来看,第一次第哦啊用 t1 对象的get()方法时返回的值是 null 。调用 set() 方法并赋值后,可以顺利取出值并打印到控制台上。类ThreadLocal解决的是变量在不同线程之间的隔离性,就是不同的线程拥有自己的值,不同线程中的值是可以通过 ThreadLocal 类进行保存的。
main 线程的兜子 ThreadLocal.ThreadLocalMap threadLocals = null;对象是由JVM进行实例化。
测试代码:
public class Test {
public static void main(String[] args) {
ThreadLocal local = new ThreadLocal();
local.set("我是任意的值");
System.out.println(local.get());
}
}
JDK 源码角度分析:
1)执行set方法的代码时,代码如下(源码在ThreadLocal.java中):
public void set(T value) {
Thread t = Thread.currentThread(); //对象t就是 main线程
ThreadLocalMap map = getMap(t); //从 main 线程中获得 ThreadLocalMap
if (map != null) { //若 map 值不等于null,进行set操作
map.set(this, value);
} else { //若 map 值等于 null, 创建map并进行set操作
createMap(t, value);
}
}
2)执行代码 ThreadLocalMap map = getMap(t); 中的 getMap(t) 的源代码如下(源码在ThreadLocal.java中):
ThreadLocal.ThreadLocalMap getMap(Thread t) {//参数 t 就是前面传入的 main线程
return t.threadLocals;
//返回 main 线程中的 threadLocals 变量对应的 ThreadLocalMap对象
}
3)threadLocals 变量对应的 ThreadLocalMap对象(源码在Thread.java中):
对象 threadLocals 数据类型就是 ThreadLocal.ThreaddLocalMap,变量threadLocals 是 Thread类中的实例变量。
4)取得 Thread 中的ThreadLocal.ThreadLocalMap后,根据 map 对象值是不是 null来决定是否对其执行 set 或 create and set 操作。
5)createMap() 方法的功能是创建一个新的 ThreadLocalMap,并在这个新的 ThreadLocalMap 中存储数据, ThreadLocalMap 中的 key 就是当前的 ThreadLocal 对象,值就是传入的value,createMap() 方法的源代码如下(源码在ThreadLocal.java中):
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
6)看一下new ThreadLocalMap(this,firstValue) 构造方法的源代码,如图(源码在ThreadLocal.java中)ThreadLocalMap是ThreadLocal中的静态内部类:
在源代码中可以发现,ThreadLocal 对象与 firstValue 封装进 Entry 对象中,并放入了 table[] 数组中,最后看一下 table[] 数组的声明。
7) table[] 数组的源码如下(源码在ThreadLocal.java中):
变量 table 就是 Entry[] 数组类型。
经过上面的 7 个步骤,成功将 value 通过 ThreadLocal 放入当前线程 currentThread() 中的ThreadLocalMap 对象里面。
8)当执行System.out.println(local.get());代码时,ThreadLocal.get()源代码如下(源码在ThreadLocal.java中):
public T get() {
Thread t = Thread.currentThread(); //t就是main线程
ThreadLocalMap map = getMap(t); //从main线程中获得Map
if (map != null) { //进入此分支,因为map不是null
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) { //进入此分支因为Entry对象不是null
@SuppressWarnings("unchecked")
T result = (T)e.value; //从Entry对象中获得value并返回
return result;
}
}
return setInitialValue(); //上面两个分支没有走,找不到返回null
}
9)上面的 8 个步骤就是就是set 和 get 的执行流程,比较麻烦,为什么不能直接向Thread类中的ThreadLocalMap 对象存取数据呢?这是不能实现的,原因如图:
变量threadLocals默认是包级访问,所以不能从外部直接访问该变量,也没有对应的 get 和 set 方法,只有用同一个包中的类可以访问threadLocals变量,而ThreadLocal和Thread恰好在同一个包中。Java.lang包下,同在lang包下的ThreadLocal可以访问Thread中的ThreadLocalMap。
由于在同一个lang包下,所以外部代码通过 ThreadLocal就可以访问Thread类中的”私密对象“ThreadLocalMap了。
实现通过ThreadLocal在每个线程中存储自己的私有数据。
public class ThreadLoalTest {
static class Tools{
public static ThreadLocal t1 = new ThreadLocal();
}
static class MyThreadA extends Thread{
@Override
public void run() {
try{
for (int i = 0; i < 10; i++) {
Tools.t1.set("A "+(i+1));
System.out.println("A get "+Tools.t1.get());
int sleepValue = (int) (Math.random()*1000);
Thread.sleep(sleepValue);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class MyThreadB extends Thread{
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
Tools.t1.set("B "+(i+1));
System.out.println(" B get "+Tools.t1.get());
int sleepValue = (int) (Math.random()*1000);
Thread.sleep(sleepValue);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThreadA a = new MyThreadA();
MyThreadB b = new MyThreadB();
a.start();
b.start();
for (int i = 0; i < 10; i++) {
Tools.t1.set("main "+(i+1));
System.out.println(" main get "+Tools.t1.get());
int sleepValue = (int)(Math.random()*1000);
Thread.sleep(sleepValue);
}
}
}
运行结果:
控制台输出的结果表明通过ThreadLocal向每一个线程存储自己的私有数据,虽然3个线程都向 t1 对象中通过的 set() 存放数据值 ,但每个人都只能取出自己的数据,不能取出别人的。
创建新的代码示例,再次实验;
public class s5 {
static class Tools{
public static ThreadLocal t1 = new ThreadLocal();
}
static class MyThreadA extends Thread{
@Override
public void run() {
try{
for (int i = 0; i < 10; i++) {
if (Tools.t1.get() == null){
Tools.t1.set("A "+(i+1));
}
System.out.println("A get "+ Tools.t1.get());
int sleepValue = (int) (Math.random()*1000);
Thread.sleep(sleepValue);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class MyThreadB extends Thread{
@Override
public void run() {
try {
for (int i = 1; i < 10; i++) {
if (Tools.t1.get()==null){
Tools.t1.set("B "+(i+1));
}
System.out.println(" B get "+ Tools.t1.get());
int sleepValue = (int) (Math.random()*1000);
Thread.sleep(sleepValue);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThreadA a = new MyThreadA();
MyThreadB b = new MyThreadB();
a.start();
b.start();
for (int i = 2; i < 10; i++) {
if (Tools.t1.get() == null){
Tools.t1.set("main "+(i+1));
}
System.out.println(" main get "+ Tools.t1.get());
int sleepValue = (int)(Math.random()*1000);
Thread.sleep(sleepValue);
}
}
}
运行结果如图:
在for循环中使用了if语句来判断当前线程的 ThreadLocalMap 中是否有数据,如果有则不再重复set,所以线程 a 存取值 1 ,线程 b 存取值 2 ,线程 main 存取值3。
在第一次调用ThreadLocal 类的 get() 方法时返回值是 null,怎么实现第一次调用 get() 不返回 null 呢?我们继续学习一下。
创建新的类继承自ThreadLocal类。
static class ThreadLocalExt extends ThreadLocal{
@Override
protected Object initialValue() {
return "我是默认值不再为null";
}
}
覆盖 initialValue() 方法具有初始值,因为ThreadLocal.java中的initialValue()方法默认返回值就是null,所以要在子类中重写。源代码如下:
运行代码如下:
private static ThreadLocalExt t1 = new ThreadLocalExt();
public static void main(String[] args) {
if (t1.get() == null){
System.out.println("从未放过值");
t1.set("我的值");
}
System.out.println(t1.get());
System.out.println(t1.get());
}
运行结果如图:
此案例仅能证明 main 线程有自己的值,那其他线程是否会有自己的初始值呢?
创建新的实例:
public class ThreadLocal33 {
static class ThreadLocalExt extends ThreadLocal{
@Override
protected Object initialValue() {
return new Date().getTime();
}
}
static class Tools{
public static ThreadLocalExt t1 = new ThreadLocalExt();
}
static class ThreadA extends Thread{
@Override
public void run() {
try{
for (int i = 0; i < 10; i++) {
System.out.println("在ThreadA线程中取值="+Tools.t1.get());
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
try {
for (int i = 0; i < 10; i++) {
System.out.println(" 在main线程中取值="+Tools.t1.get());
Thread.sleep(100);
}
Thread.sleep(5000);
ThreadA threadA = new ThreadA();
threadA.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
子线程和父线程有各自的默认值。
ThreadLocalMap中的静态内置类Entry是弱引用类型,源代码如图:
弱引用的特点是,只要垃圾回收器扫描时发现弱引用类型的对象,则不管内存是否足够,都会回收弱引用的对象。也就是只要执行 gc 操作,ThreadLocal对象就立即销毁,代表key的值ThreadLocal对象会随着gc操作而销毁,释放内存空间,但value值却不会随着gc操作而销毁 ,这会出现内存溢出。
public class ThreadLocal_Remove {
static class MyThreadLocal extends ThreadLocal{
private static AtomicInteger count = new AtomicInteger(0);
@Override
protected void finalize() throws Throwable {
System.out.println("MyThreadLocal finalize()"+count.addAndGet(1));
}
}
static class Userinfo{
private static AtomicInteger count = new AtomicInteger(0);
@Override
protected void finalize() throws Throwable {
System.out.println("------------Userinfo protect void finalize"+count);
}
}
public static void main(String[] args) {
for (int i = 0; i < 9000; i++) {
MyThreadLocal threadLocal = new MyThreadLocal();
Userinfo userinfo = new Userinfo();
threadLocal.set(userinfo);
//threadLocal.remove();
}
MyThreadLocal threadLocal = new MyThreadLocal();
System.out.println("9000 end!");
List list = new ArrayList();
for (int i = 0; i < 900000000; i++) {
String newString = new String(""+(i+1));
Thread.yield();
Thread.yield();
Thread.yield();
Thread.yield();
}
//打印threadLocal实现强引用
System.out.println("zzzzzzzzzzzzz"+threadLocal);
}
}
程序运行后,在jvisualvm.exe工具中可以查看到9000个MyThreadLocal类的对象全部被 gc 垃圾回收了,因为它们是弱引用类型,只有一个强引用的MyThreadLocal对象得以保留。 如图:
但是Userinfo的实例却没有被 gc 回收,整整9000个Userinfo对象都保存在内存中,如图:
如果Userinfo对象数量更多,则一定会出现内存溢出,所以 static class ThreadLocalMap类中不用的数据要使用ThreadLocal类的remove()方法进行清楚,实现Userinfo类对象的垃圾回收,释放内存。
运行代码如下:
public static void main(String[] args) {
for (int i = 0; i < 9000; i++) {
MyThreadLocal threadLocal = new MyThreadLocal();
Userinfo userinfo = new Userinfo();
threadLocal.set(userinfo);
threadLocal.remove();
}
MyThreadLocal threadLocal = new MyThreadLocal();
System.out.println("9000 end!");
List list = new ArrayList();
for (int i = 0; i < 900000000; i++) {
String newString = new String(""+(i+1));
Thread.yield();
Thread.yield();
Thread.yield();
Thread.yield();
}
//打印threadLocal实现强引用
//至少在内存中保留一个threadLocal对象
System.out.println("zzzzzzzzzzzzz"+threadLocal);
}
在代码中对ThreadLocal对象调用了 remove()方法,清楚不使用的对象,再释放内存资源。
程序运行后内存只有一个MyThreadLocal类的实例,并且9000个Userinfo类的实例也被回收了。
此实验的结论就是:当ThreadLocalMap中的数据不再使用时,要手动执行ThreadLocal类的remove()方法,清楚数据,释放内存空间,不然会出现内存溢出。