一、简单使用
-
代码实现
public class ThreadLocalTest { static ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { new Thread(()->{ //在线程内部设值 threadLocal.set("threadA"); print(); },"A").start(); new Thread(()->{ //在线程内部设值 threadLocal.set("threadB"); print(); },"B").start(); } public static void print(){ String str = threadLocal.get(); System.out.println(Thread.currentThread().getName()+"->"+str); threadLocal.remove(); str = threadLocal.get(); System.out.println(Thread.currentThread().getName()+"->"+str); } }
-
分析
上面的代码会输出类似下面的内容。可能线程的执行顺序会变
A->threadA A->null B->threadB B->null
线程A删除了值,不影响线程B中的值。
二、ThreadLocal原理分析
-
查看set方法
/** set方法是在当前线程的threadLocals字段中设值 key为ThreadLocal对象 */ public void set(T value) { Thread t = Thread.currentThread(); //获取当前线程对象 ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else //第一次set值时 创建ThreadLocalMap对象 createMap(t, value); } /** 获取当前线程的threadLocals字段 该字段类似于一个HashMap 该字段在Thread类中的定义是包可见 ThreadLocal.ThreadLocalMap threadLocals = null; //没有指定访问权限 默认包可见 Thread类和ThreadLocal类都是java.lang包下 所以能够访问这个字段 */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; } //创建ThreadLocalMap对象 void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
Thread类的threadLocals字段在其他包中是不能访问的
所以不能在自定义类中操作这个字段。
-
查看get方法
/** 从该线程的threadLocals字段中获取值 key为ThreadLocal对象 */ public T get() { Thread t = Thread.currentThread(); //获取当前线程对象 ThreadLocalMap map = getMap(t); //获取线程对象中的ThreadLocalMap对象字段 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //如果Thread类的threadLocals字段为null,则初始化,设值一个null值,并返回null return setInitialValue(); }
-
查看remove方法
/** 从该线程的threadLocals字段中移除元素 key为ThreadLocal对象 */ public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
-
以上分析可知,ThreadLocal类操作的其实是Thread类自身的threadLocals字段。
threadLocals字段类似于HashMap,保存key-value键值对。
设置的值其实是保存在Thread类的存储空间中的。
所以当不再使用这个值时,应该remove掉。
ThreadLocal只是类似一个工具类。
-
ThreadLocal设置的值不存在继承性
比如在main主线程中new一个线程A,主线程main和线程A都具有自己的threadLocals字段。
在main线程中set的值在A线程中获取不到。
public class UnInheritableThreadLocalTest { //使用 ThreadLocal static ThreadLocal<String> threadLocal = new ThreadLocal(); public static void main(String[] args) { threadLocal.set("main value"); new Thread(()->{ String str = threadLocal.get(); System.out.println(Thread.currentThread().getName()+"->" + str); },"A").start(); } }
打印
A->null
三、InheritableThreadLocal 实现线程本地变量继承性
-
先看代码
public class InheritableThreadLocalTest { //使用 InheritableThreadLocal static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal(); public static void main(String[] args) { threadLocal.set("main value"); new Thread(()->{ String str = threadLocal.get(); System.out.println(Thread.currentThread().getName()+"->" + str); },"A").start(); } }
打印
A->main value
可见,InheritableThreadLocal的变量在父线程中设置的值,在子线程中也可以获取。
-
分析
InheritableThreadLocal 继承自 ThreadLocal,并重写了下面三个方法
public class InheritableThreadLocal<T> extends ThreadLocal<T> { //调用ThreadLocal类的这个方法会抛出异常 protected T childValue(T parentValue) { return parentValue; } //返回的是Thread类的inheritableThreadLocals字段 ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; } //创建的是Thread类的inheritableThreadLocals字段变量 void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); } }
子线程在创建时调用了Thread类的init方法
//线程初始化会调用这个方法 private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null, true); } //最终初始化调用方法 private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { if (name == null) { throw new NullPointerException("name cannot be null"); } this.name = name; Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); if (g == null) { /* Determine if it's an applet or not */ /* If there is a security manager, ask the security manager what to do. */ if (security != null) { g = security.getThreadGroup(); } /* If the security doesn't have a strong opinion of the matter use the parent thread group. */ if (g == null) { g = parent.getThreadGroup(); } } /* checkAccess regardless of whether or not threadgroup is explicitly passed in. */ g.checkAccess(); /* * Do we have the required permissions? */ if (security != null) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } g.addUnstarted(); this.group = g; this.daemon = parent.isDaemon(); this.priority = parent.getPriority(); if (security == null || isCCLOverridden(parent.getClass())) this.contextClassLoader = parent.getContextClassLoader(); else this.contextClassLoader = parent.contextClassLoader; this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); this.target = target; setPriority(priority); //这里会初始化子线程的inheritableThreadLocals字段 if (inheritThreadLocals && parent.inheritableThreadLocals != null) //因为父线程的inheritableThreadLocals不为null 会调用下面语句创建子线程的 inheritableThreadLocals字段 this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; /* Set thread ID */ tid = nextThreadID(); }
Thread类的init方法调用了ThreadLocal类的下列方法 并把父线程的inheritableThreadLocals传递了过来
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); } //最终会把父线程中的inheritableThreadLocals字段的内容 //拷贝到子类的inheritableThreadLocals字段中 //这样就实现了继承传递性 private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } }
-
这里补充一点:默认情况下new 的线程是一个用户线程。可以调用线程的setDaemon(true)方法设置线程为守护线程。
最典型的守护线程就是GC线程。
-
java程序中如果存在用户线程,则程序一直运行。如果只剩下守护线程,则程序会退出执行。