1 概述
变量值的共享可以使用public static 的声明方式,所有的线程都是用同一个public static变量,那如果想实现每一个线程都有自己的变量该如何解决呢?JDK提供的ThreadLocal就派上用场了。
ThreadLocal类主要的作用就是将数据放入当前线程对象中的Map里,这个Map类是Thread类的实例变量。ThreadLocal类自己不管理也不存储任何数据,它只是数据和Map之间的中介和桥梁,通过ThreadLocal将数据放入Map中,执行流程如下:
数据——>ThreadLocal——>currentThread()——>Map
执行后每个线程中的Map就存储自己的数据,Map中的key存储的是ThreadLocal对象,value就是存储的值,说明ThreadLocal和值之间是一对一的关系,一个ThreadLocal对象只能关联一个值。每个线程中Map的值只对向前线程可见,其他线程不可以访问当前线程对象中Map的值。内存结构如下:
2 get()方法与null
如果从未在Thread中的Map存储 ThreadLocal对象对应的值,则get()方法返回null。
public class Run1 {public static ThreadLocal t1 = new ThreadLocal();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());}
}
ThreadLocal类解决的是变量在不同线程中的隔离性,也就是不同线程拥有自己的值,不同线程中的值是可以通过ThreadLocal类进行保存的。
3 ThreadLocal类存取数据流程分析
运行测试程序:
public class Test {public static void main(String[] args) {ThreadLocal local = new ThreadLocal();local.set("value");System.out.println(local.get());}
}
从JDK源码角度来分析一下ThreadLocal类执行存取操作的流程。
首先看一下数据如何存入到ThreadLocal中的。
(1)执行ThreadLocal.set("value")代码时,ThreadLocal代码如下:
public void set(T value) {//对象t就是main线程Thread t = Thread.currentThread();//从main线程中获取ThreadLocalMap ThreadLocalMap map = getMap(t);if (map != null) {//如果map不等于null,则set操作map.set(this, value);} else {//如果map等于null,则先执行创建,在执行setcreateMap(t, value);}}
(2)ThreadLocalMap map = getMap(t); 源码如下:
ThreadLocalMap getMap(Thread t) {//参数t就是前面传入的main线程return t.threadLocals;//返回main线程中threadLocals变量对应的ThreadLocalMap对象}
对象threadLocals数据类型就是ThreadLocal.ThreadLocalMap,变量threadLocals是Thread类中的实例变量。
(3)取得Thread中的ThreadLocal.ThreadLocalMap后,根据map对象值是不是null来决定是否对其执行set或create and set操作。
(4)createMap()方法的功能是创建一个新的ThreadLocalMap,并在这个新的ThreadLocalMap里存储数据,ThreadLocalMap中的key就是当前ThreadLocal对象,值就是传入的value,createMap()方法的源码如下:
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}
在实例化ThreadLocalMap的时候,向构造方法传入thi和firstValue,其中,this就是当前ThreadLocal对象,firstValue就是调用ThreadLocal对象时set()方法传入的参数值。
new ThreadLocalMap(this, firstValue)的源码是:
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY);}
在源码中可以发现,将ThreadLocal对象与firstValue封装进Entry对象中,并放入table[]数组。
再看一下get()的执行流程。
(1)当执行 local.get() 代码时,ThreadLocal.get()源码如下:
public T get() {Thread t = Thread.currentThread();//t 就是main线程ThreadLocalMap map = getMap(t);//从main线程中获取ThreadLocalMap if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);//如果map不等于null,获取Entry对象if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();}
总结:上面的几个步骤就是set和get的执行流程,比较麻烦。为什么不能直接向Thread类中的ThreadLocalMap对象存取数据呢?这是无法实现的,原因参考下面代码:
ThreadLocal.ThreadLocalMap threadLocals = null;
变量 threadLocals 默认是包级访问,所以不能从外部直接访问该变量,也没有对应的get和set方法,只有用同一个包中的类可以访问threadLocals变量,而ThreadLocal和Thread恰好在同一个包中(都在java.lang包下)。
4 验证线程变量的隔离性
本节将实现通过使用ThreadLocal在每个线程中存储自己的私有数据。
public class Tools {public static ThreadLocal t1 = new ThreadLocal();
}
public class MyThreadA extends Thread{@Overridepublic 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() * 10000);Thread.sleep(sleepValue);}}catch (InterruptedException e){e.printStackTrace();}}
}
public class MyThreadB extends Thread{@Overridepublic 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() * 10000);Thread.sleep(sleepValue);}}catch (InterruptedException e){e.printStackTrace();}}
}
public class Run1 {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() * 10000);Thread.sleep(sleepValue);}}
}
控制台输出的结果表示通过ThreadLocal向每个线程存储自己的私有数据,虽然3个线程都向t1存放数据,但是每个线程仅能取出自己的数据,不能取出其他线程存放的数据 。
5 解决get()返回null的问题
新建ThreadLocalExt.java,继承ThreadLocal类,并覆盖 initialValue() 方法
public class ThreadLocalExt extends ThreadLocal{@Overrideprotected Object initialValue(){return "我是默认值,第一次get不再为null";}
}
覆盖initialValue()方法具有初始值,因为ThreadLocal.java中的initialValue方法默认返回值就是null,所以要在子类中重写。源码如下:
protected T initialValue() {return null;}
public class Run1 {public 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());}
6 验证重写initialValue()方法的隔离性
public class Tools {public static ThreadLocalExt t1 = new ThreadLocalExt();
}
public class ThreadLocalExt extends ThreadLocal{@Overrideprotected Object initialValue() {return new Date().getTime();}
}
public class ThreadA extends Thread{@Overridepublic 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 class Run1 {public static void main(String[] args) {try {for (int i = 0; i < 10; i++) {System.out.println("在Main线程中取值 = " + Tools.t1.get());}Thread.sleep(2000);ThreadA threadA = new ThreadA();threadA.start();}catch (InterruptedException e){e.printStackTrace();}}
}
7 使用remove()方法的必要性
ThreadLocalMap中的静态内置类Entry是弱引用类型,源码如下:
static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}
弱引用的特点是,只要垃圾回收器扫描时发现弱引用的对象,就不管内存是否足够,都会回收弱引用的对象。也就是只要执行gc操作,ThreadLocal对象就会立即销毁,代表key的值ThreadLocal对象会随着gc操作而销毁,释放内存空间,但value值却不会随着gc操作而销毁,这会出现内存溢出。如果对象数量过多,对于ThreadLocalMap类中不用的数据使用ThreadLocal类的remove方法进行清除,实现业务对象的垃圾回收,释放内存。