赞
踩
ThreadLocal是一个关于创建线程局部变量的类。
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。
一般在多线程开发中,为了确保某单个线程内的变量是独立的,不被其他线程访问到的时候就用到ThreadLocal了。
// 创建线程,支持泛型
ThreadLocal<String> strThreadLocal = new ThreadLocal<>();
// set方法
strThreadLocal.set("test");
// get方法
strThreadLocal.get();
public class Main { public static void main(String[] args) { testThreadLocal(); } public static void testThreadLocal() { Thread t = new Thread() { ThreadLocal<String> strThreadLocal = new ThreadLocal<String>();//该变量子线程不会 String strTest="哈哈哈";// 该变量子线程会拿到数据 @Override public void run() { super.run(); strThreadLocal.set("weTest"); String s = strThreadLocal.get(); System.out.println("main-1:" + s); System.out.println("main-strTest-1:" + strTest); new Thread() { @Override public void run() { String s1 = strThreadLocal.get(); System.out.println("child-1:" + s1); System.out.println("child-strTest-1:" + strTest); } }.start(); String s1 = strThreadLocal.get(); System.out.println("main-2:" + s1); System.out.println("main-strTest-2:" + strTest); } }; t.start(); } }
打印结果
main-1:weTest
main-strTest-1:哈哈哈
main-2:weTest
main-strTest-2:哈哈哈
child-1:null
child-strTest-1:哈哈哈
为ThreadLocal设置默认的get初始值,需要重写initialValue方法,下面是一段代码,我们将默认值修改成了线程的名字
public class Main { public static void main(String[] args) { testInitValueThreadLocal(); } public static void testInitValueThreadLocal(){ /** * 为ThreadLocal设置默认的get初始值,需要重写initialValue方法,下面是一段代码,我们将默认值修改成了线程的名字 */ ThreadLocal<String> mThreadLocal = new ThreadLocal<String>() { @Override protected String initialValue() { return Thread.currentThread().getName(); } }; System.out.println(mThreadLocal.get()); } }
在Android中,Looper类就是利用了ThreadLocal的特性,保证每个线程只存在一个Looper对象。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
以set方法从起始看一看ThreadLocal的实现原理。
下面是ThreadLocal的set方法,大致意思为
源码如下:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
下面是一个利用Thread对象作为句柄获取ThreadLocalMap对象的代码:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
上面的代码获取的实际上是Thread对象的threadLocals变量,可参考下面代码:
class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
而如果一开始设置,即ThreadLocalMap对象未创建,则新建ThreadLocalMap对象,并设置初始值。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
总结:实际上ThreadLocal的值是放入了当前线程的一个ThreadLocalMap实例中,所以只能在本线程中访问,其他线程无法访问。
在Java中,栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。而堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。
问:那么是不是说ThreadLocal的实例以及其值存放在栈上呢?
其实不是,因为ThreadLocal实例实际上也是被其创建的类持有(更顶端应该是被线程持有)。而ThreadLocal的值其实也是被线程实例持有。
它们都是位于堆上,只是通过一些技巧将可见性修改成了线程可见。
关于堆和栈的比较,请参考Java中的堆和栈的区别
既然上面提到了ThreadLocal只对当前线程可见,是不是说ThreadLocal的值只能被一个线程访问呢?
使用InheritableThreadLocal可以实现多个线程访问ThreadLocal的值。
如下,我们在主线程中创建一个InheritableThreadLocal的实例,然后在子线程中得到这个InheritableThreadLocal实例设置的值。
public class Main { public static void main(String[] args) { testInheritableThreadLocal(); } /** * ThreadLocal只对当前线程可见,是不是说ThreadLocal的值只能被一个线程访问呢? * 使用InheritableThreadLocal可以实现多个线程访问ThreadLocal的值。 * 我们在主线程中创建一个InheritableThreadLocal的实例,然后在子线程中得到这个InheritableThreadLocal实例设置的值。 * * 使用InheritableThreadLocal可以将某个线程的ThreadLocal值在其子线程创建时传递过去。因为在线程创建过程中,有相关的处理逻辑。 */ public static void testInheritableThreadLocal() { final ThreadLocal threadLocal = new InheritableThreadLocal(); threadLocal.set("weTest"); System.out.println("mailThread=>" + threadLocal.get()); Thread t = new Thread() { @Override public void run() { super.run(); System.out.println("childThread=>" + threadLocal.get()); threadLocal.set("weTest2"); System.out.println("childThread=>" + threadLocal.get()); } }; t.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("mailThread=>" + threadLocal.get()); } }
打印输出:
mailThread=>weTest
childThread=>weTest
childThread=>weTest2
mailThread=>weTest
使用InheritableThreadLocal可以将某个线程的ThreadLocal值在其子线程创建时传递过去。因为在线程创建过程中,有相关的处理逻辑。
//Thread.java
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
//code goes here
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
上面代码就是在线程创建的时候,复制父线程的inheritableThreadLocals的数据。
有网上讨论说ThreadLocal会导致内存泄露,原因如下:
上面的逻辑是清晰的,可是ThreadLocal并不会产生内存泄露,因为ThreadLocalMap在选择key的时候,并不是直接选择ThreadLocal实例,而是ThreadLocal实例的弱引用。
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; } } }
所以实际上从ThreadLocal设计角度来说是不会导致内存泄露的。关于弱引用,了解更多,请访问
译文:理解Java中的弱引用
package threadlocal; public class Main { public static void main(String[] args) { // testThreadLocal(); // testInitValueThreadLocal(); testInheritableThreadLocal(); } /** * ThreadLocal的基本使用和比较 */ public static void testThreadLocal() { Thread t = new Thread() { ThreadLocal<String> strThreadLocal = new ThreadLocal<String>();//该变量子线程不会 String strTest = "哈哈哈";// 该变量子线程会拿到数据 @Override public void run() { super.run(); strThreadLocal.set("weTest"); String s = strThreadLocal.get(); System.out.println("main-1:" + s); System.out.println("main-strTest-1:" + strTest); new Thread() { @Override public void run() { String s1 = strThreadLocal.get(); System.out.println("child-1:" + s1); System.out.println("child-strTest-1:" + strTest); } }.start(); String s1 = strThreadLocal.get(); System.out.println("main-2:" + s1); System.out.println("main-strTest-2:" + strTest); } }; t.start(); } /** * ThreadLocal的默认初始值 * 为ThreadLocal设置默认的get初始值,需要重写initialValue方法,下面是一段代码,我们将默认值修改成了线程的名字 */ public static void testInitValueThreadLocal() { ThreadLocal<String> mThreadLocal = new ThreadLocal<String>() { @Override protected String initialValue() { return Thread.currentThread().getName(); } }; System.out.println(mThreadLocal.get()); } /** * ThreadLocal只对当前线程可见,是不是说ThreadLocal的值只能被一个线程访问呢? * 使用InheritableThreadLocal可以实现多个线程访问ThreadLocal的值。 * 我们在主线程中创建一个InheritableThreadLocal的实例,然后在子线程中得到这个InheritableThreadLocal实例设置的值。 * * 使用InheritableThreadLocal可以将某个线程的ThreadLocal值在其子线程创建时传递过去。因为在线程创建过程中,有相关的处理逻辑。 */ public static void testInheritableThreadLocal() { final ThreadLocal threadLocal = new InheritableThreadLocal(); threadLocal.set("weTest"); System.out.println("mailThread=>" + threadLocal.get()); Thread t = new Thread() { @Override public void run() { super.run(); System.out.println("childThread=>" + threadLocal.get()); threadLocal.set("weTest2"); System.out.println("childThread=>" + threadLocal.get()); } }; t.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("mailThread=>" + threadLocal.get()); } }
https://droidyue.com/blog/2016/03/13/learning-threadlocal-in-java/
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。