Java中ThreadLocal的使用
作者:手写情书
1.预备知识-静态内部类
静态内部类的加载时机?他和外部类的加载有没有什么关系
静态内部类的加载是在程序中调用静态内部类的时候加载的,和外部类的加载没有必然关系, 但是在加载静态内部类的时候 发现外部类还没有加载,那么就会先加载外部类 ,加载完外部类之后,再加载静态内部类.(初始化静态变量和静态代码块etc),如果在程序中单纯的使用 外部类,并不会触发静态内部类的加载
扩展 :
一个类内部有静态内部类和非静态内部类 , 静态内部类和非静态内部类一样,都是在被调用时才会被加载 ,不过在加载静态内部类的过程中如果没有加载外部类,也会加载外部类,静态变量,静态方法,静态块等都是类级别的属性,而不是单纯的对象属性.他们在类第一次被使用时被加载 (记住,是一次使用,不一定是实例化),我们可以简单得用 类名.变量 或者 类名.方法来调用它们, 与调用没有被static 修饰过变量和方法不同的是:一般变量和方法是用当前对象的引用(即this)来调用的, 静态的方法和变量则不需要.从一个角度上来说,它们是共享给所有对象的,不是一个角度私有. 这点上,静态内部类也是一样的
类的加载时机:(暂时的认知里是四种) new 一个类的时候,调用类内部的 静态变量,调用类的静态方法,调用类的 静态内部类
public class OuterClass { public static String OUTER_DATE = "外部类静态变量加载时间 "+System.currentTimeMillis(); static { System.out.println("外部类静态块加载时间:" + System.currentTimeMillis()); } public OuterClass() { System.out.println("外部类构造函数时间:" + System.currentTimeMillis()); } static class InnerStaticClass{ public static String INNER_STATIC_DATE = "静态内部类静态变量加载时间 "+System.currentTimeMillis(); private String name; static { System.out.println("静态内部类静态代码块加载时间:" + System.currentTimeMillis()); } } class InnerClass { public String INNER_DATE = ""; public InnerClass() { INNER_DATE = "非静态内部类构造器加载时间"+System.currentTimeMillis(); } } public static void main(String[] args) { OuterClass outer = new OuterClass(); System.out.println("非静态内部类加载时间: "+outer.new InnerClass().INNER_DATE); /** * 内部静态类可以直接用,不需要new * 静态内部类的加载是代码中需要静态内部类的时候才加载,而不是和外部类一起加载的 * 加载静态内部类之前,先把外部类的静态变量和静态代码块先执行完 * 执行完外部类的代码后,再执行静态内部类的 静态变量和静态代码块 * 静态内部类的 静态变量和静态代码块执行完后,然后执行业务代码 * new 外部类的时候 。外部类的静态代码块和静态变量先执行,外部类构造函数后执行 */ System.out.println("静态内部类加载时间____:"+InnerStaticClass.INNER_STATIC_DATE); } } //什么时候考虑使用静态内部类? //A类中需要一个B类,但是B类只为A类服务,这种情况不需要将B类单独剥离,只需要在A内部即可 //一个类的构建有非常多参数或者十分复杂的一个对象的时候--引申到了建造者模式
2.ThredLocal
2.1ThrealLocal简介
ThrealLocal叫做现成变量,意思就是ThradLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量.ThradLocal为变量在每个线程中都创建了副本,那么每个线程可访问自己内部的副本变量.
- 因为每个Thread内有自己的实例副本,且副本只能由当前Thread使用.这也是ThreadLocal命名的由来
- 既然每个Thred都有自己的实例副本,且其他Thread不可访问,那么不存在多线程的共享问题
总的来说,ThredLocal适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程隔离而在方法或类间共享的场景
2.2 ThreadLocal与Synchronized的区别
ThreadLocal其实是与线程绑定的一个变量.ThreadLocal和Synchonized都用于解决多线程并发访问
但是ThreadLocal与synchronized有本质的区别 :
- Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离
- Synchronized是利用锁的机制,使变量或代码块在某一时刻只能被一个线程访问.而ThredLocal为每一个线程提供了变量的副本,使得每个线程某一个时间访问到的不是同一个对象,这样就隔离了多个现线程岁数据的共享, 而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享
一句话理解ThreadLocal,向ThreadLocal里面存东西就是向它里面的Map存东西的,然后ThreadLocal把这个Map挂到当前的线程底下,这样Map就只属于这个线程了
2.3 ThreadLocal的简单使用
public class ThreadLocaDemo { private static ThreadLocal<String> localVar = new ThreadLocal<String>(); static void print(String str) { //打印当前线程中本地内存中本地变量的值 System.out.println(str + " :" + localVar.get()); //清除本地内存中的本地变量 localVar.remove(); } public static void main(String[] args) throws InterruptedException { new Thread(new Runnable() { public void run() { ThreadLocaDemo.localVar.set("local_A"); print("A"); //打印本地变量 System.out.println("after remove : " + localVar.get()); } },"A").start(); Thread.sleep(1000); new Thread(new Runnable() { public void run() { ThreadLocaDemo.localVar.set("local_B"); print("B"); System.out.println("after remove : " + localVar.get()); } },"B").start(); } }
2.4ThrealLocal的内存泄漏问题
public void set(T value) { //1、获取当前线程 Thread t = Thread.currentThread(); //2、获取线程中的属性 threadLocalMap ,如果threadLocalMap 不为空, //则直接更新要保存的变量值,否则创建threadLocalMap,并赋值 ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else // 初始化thradLocalMap 并赋值 createMap(t, value); }
static class ThreadLocalMap { /** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } }
可看出ThreadLocalMap是ThreadLocal的内部静态类,而它的构成主要是用Entry来保存数据 ,而且还是继承的弱引用.在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value.
//这个是threadlocal 的内部方法 void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } //ThreadLocalMap 构造方法 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); }
public T get() { //1、获取当前线程 Thread t = Thread.currentThread(); //2、获取当前线程的ThreadLocalMap ThreadLocalMap map = getMap(t); //3、如果map数据为空, if (map != null) { //3.1、获取threalLocalMap中存储的值 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //如果是数据为null,则初始化,初始化的结果,TheralLocalMap中存放key值为threadLocal,值为null 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; }
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
remove方法,直接将ThrealLocal 对应的值从当前相差Thread中的ThreadLocalMap中删除.为什么要删除,这涉及到内存泄露的问
实际上 ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉
所以如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,这样一来 ThreadLocalMap中使用这个 ThreadLocal 的 key 也会被清理掉。但是,value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value
ThreadLocal其实是与线程绑定的一个变量,如此就会出现一个问题:如果没有将ThreadLocal内的变量删除(remove)或替换,它的生命周期将会与线程共存。通常线程池中对线程管理都是采用线程复用的方法,在线程池中线程很难结束甚至于永远不会结束,这将意味着线程持续的时间将不可预测,甚至与JVM的生命周期一致。举个例字,如果ThreadLocal中直接或间接包装了集合类或复杂对象,每次在同一个ThreadLocal中取出对象后,再对内容做操作,那么内部的集合类和复杂对象所占用的空间可能会开始持续膨胀
我从这个图中我们可以非常直观的看出,ThreadLocalMap其实是Thread线程的一个属性值,而ThreadLocal是维护ThreadLocalMap这个属性指的一个工具类。
Thread线程可以拥有多个ThreadLocal维护的自己线程独享的共享变量(这个共享变量只是针对自己线程里面共享)
到此这篇关于Java中ThreadLocal的使用的文章就介绍到这了,更多相关ThreadLocal的使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!