线程局部变量(一般,ThreadLocal变量是private static修饰的,此时ThreadLocal变量至关于成为了线程内部的全局变量)安全
变量在线程内部共享,线程间无关多线程
再具体点,能够分为两类:ide
为何这么说呢?下面看4个问题:工具
(1)对象为何要是单例的?this
若是对象不是单例的,那么大能够每次都new一个对象,而后对用到属性赋值就行,代码以下:线程
public class Service { private String key; void A() { // 代码实现,中间用到key } void B() { // 代码实现,中间用到key } // 省略get和set方法 }
在使用时,每一个线程都new Service(),并对key赋值,而后调用其中的方法就好了,保证方法A和B用的key都是一个值。code
(2)单例对象的属性共享对象
若是但愿单例对象中的某个属性能够被共享,那么将属性声明为static就好了:blog
public class Service { private static String key; // 省略其余方法 }
上面的实现确实保证了全部方法都能使用key,然而,在多线程环境下,key是不安全的。内存
(3)单例对象在线程内属性共享,不一样线程间相互不影响
这就轮到ThreadLocal上场了:
public class Service { private static ThreadLocal<String> key = new ThreadLocal<String>() { protected String initialValue() { return Thread.currentThread().getName(); } }; public void A() { System.out.println("methodA: " + key.get()); key.set("methodA: " + key.get()); } public void B() { System.out.println("methodB: " + key.get()); } }
使用方式:
public class ThreadLocalTest { public static void main(String[] args) { final Service service = new Service(); //模拟单例对象的使用 new Thread(new Runnable() { @Override public void run() { service.A(); service.B(); } }).start(); new Thread(new Runnable() { @Override public void run() { service.A(); service.B(); } }).start(); } }
运行结果:
methodA: Thread-0 methodB: methodA: Thread-0 methodA: Thread-1 methodB: methodA: Thread-1
(4)工具类中线程共享,线程间无关
工具类的代码:
public final class XUtil { private static ThreadLocal<String> key = new ThreadLocal<String>(); private XUtil() { } public static void A() { // 实现 } public static void B() { // 实现 } }
在使用XUtil时,每一个线程中key可使用,不一样线程间不受影响。
为何一个static的变量(即:类变量)能够作到:线程内共享,线程间无关?
看下ThreadLocal中的get源码:
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if(map!=null) { ThreadLocalMap.Entry e = map.getEntry(this); if(e!=null) return (T)e.value; } return setInitialValue(); }
关键就在getMap()方法:
ThreadLocalMap getMap(Thread t) { return t.threadlocals; }
取的是当前线程内部的threadLocals属性。
查看Thread类:
ThreadLocal.ThreadLocalMap threadLocals = null;
threadLocals是ThreadLocal类中自定义的一个HashMap类。
原来数据就存在当前线程内部,天然就能作到线程内共享,线程间无关了。
接着看下set的源码:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(); if(map != null) map.set(this, value); else createMap(t,value); }
不管是map.set(this, value)仍是createMap(t, value),最后都是将数据保存到当前线程中的那个HashMap中:将ThreadLocal变量做为key,value就是要保存的数据。
在前面看到数据最终是存在线程内部的一个Map中的:
ThreadLocal.ThreadLocalMap threadLocals = null;
且key是ThreadLocal变量的引用,在get方法:
ThreadLocalMap.Entry e = map.getEntry(this); // this为当前对象的引用
当ThreadLocal变量被销毁时,而当前线程又持有ThreadLocal的引用,那么ThreadLocal就不会被回收,致使内存泄露。
然而,编写JDK的大牛们考虑到了这个问题,所以将ThreadLocalMap的key设置为弱引用:
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal> { Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } } }
对ThreadLocal变量的弱引用,在GC时,ThreadLocal变量就会被回收。
因而,在当前线程的本地变量HashMap中,原来ThreadLocal做为key的,如今变成null做为key了,该key-value变得不可访问了,若是当前线程一直不结束,那么value对应的对象就没法释放,也就是发生内存泄露了。
参考文献: