ThreadLocal,线程局部变量。ThreadLocal经过为每一个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在不少状况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。java
在Java的多线程编程中,为保证多个线程对共享变量的安全访问,一般会使用synchronized来保证同一时刻只有一个线程对共享变量进行操做。这种状况下能够将类变量放到ThreadLocal类型的对象中,使变量在每一个线程中都有独立拷贝,不会出现一个线程读取变量时而被另外一个线程修改的现象。最多见的ThreadLocal使用场景为用来解决数据库链接、Session管理等。数据库
由结构图可知,ThreadLocal的一些核心机制:编程
所以对于不一样的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,造成了副本的隔离,互不干扰。安全
Thread线程内部的Map类以下:多线程
public class Thread implements Runnable { ...... // 与此线程有关的ThreadLocal值,由ThreadLocal类维护。 ThreadLocal.ThreadLocalMap threadLocals = null; ...... }
ThreadLocal类提供如下方法:并发
get()方法:用于获取当前线程的副本变量值。dom
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } 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; }
set()方法:用于保存当前线程的副本变量值。ide
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocal.ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue); }
remove()方法:移除当前线程的副本变量值。this
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,用独立的方式实现了Map的功能,其内部的Entry也独立实现。线程
在ThreadLocalMap中,使用Entry来保存K-V结构数据。可是Entry中key只能是ThreadLocal对象,这点被Entry的构造方法已经限定死了。
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
ThreadLocalMap中使用的key为ThreadLocal的弱引用,而value是强引用。因此,若是ThreadLocal没有被外部强引用的状况下,在垃圾回收时key会被清理掉而value不会被清理掉。这样,ThreadLocalMap中就会出现key为null的Entry。加入不作任何措施,value永远没法被GC回收,这种时候可能形成内存泄漏。
所以为了不内存泄漏问题,在调用ThreadLocal的get()、set()方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就能够被回收。
ThreadLocal处理SimpleDateFormat线程不安全问题。
import java.text.SimpleDateFormat; import java.util.Random; /** * ThreadLocal处理SimpleDateFormat线程不安全问题 */ public class ThreadLocalDemo implements Runnable { private static final ThreadLocal<SimpleDateFormat> FORMATTER = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm")); public static void main(String[] args) throws InterruptedException { ThreadLocalDemo demo = new ThreadLocalDemo(); for (int i = 0; i < 10; i++) { Thread t = new Thread(demo, "" + i); Thread.sleep(new Random().nextInt(1000)); t.start(); } } @Override public void run() { System.out.println("Thread Name = " + Thread.currentThread().getName() + "; Default Formatter = " + FORMATTER.get().toPattern()); try { Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } FORMATTER.set(new SimpleDateFormat()); System.out.println("Thread Name = " + Thread.currentThread().getName() + "; Formatter = " + FORMATTER.get().toPattern()); } }
执行结果:
由输出可知,Thread0已经改变了FORMATTER的值,但Thread1的初始化值仍然与默认初始化值相同,其余线程也同样。