Java并发编程系列(六)---- ThreadLocal使用及源码分析
2017-02-11 00:56
507 查看
ThreadLocal成为线程本地变量,从名字大概可以猜出是和线程本地变量有关的,提供的方法主要有这么几个
执行结果
可以看到,虽然local实例是两个线程共享的,但是他们之间设值以及取值是不会相互影响的,这就是本地线程变量。
我们在看看initialValue是怎么用的,把上面的例子中的
改为
那么执行结果为:
可以看到,如果重写了initialValue,那么当线程没有设置值或者已经移除了,那么会将initialValue返回值作为默认值。
看看ThreadLocal类的成员变量
在Thread中:
ThreadLocalMap为ThreadLocal的一个静态内部类,初始时获取到线程的map为null,也就是线程的threadLocals是延迟初始化的,线程没有用到ThreadLocal功能的时候就不创建对象,避免冗余,充分体现了JDK大神深厚的编码功底。我们先看看createMap的实现:
这里是创建当前线程的threadLocals变量,注意这里传进去的第一个参数是this,也就是当前的threadlocal实例对象。看看ThreadLocalMap内部类
这里的主要工作就是给当前线程创建ThreadLocalMap实例,把当前threadlocal对象和需要放进去的值关系储存在ThreadLocalMap实例的table中。如果说一个线程用了三个threadlocal对象,比如:
那么local1和”ThreadLocal1”作为一个entry放在线程threadLocals(ThreadlocalMap实例)变量的table中,local2和”ThreadLocal2”作为一个entry也放该table中,local2和”ThreadLocal2”作为一个entry也放在table中。当然后面两个entry不是在create方法中放进去的,以为第一次create后,getMap方法就已经不在返回null,而是执行map.set()
看看map.set():
这也就解释了前面为什么没有set之前或者remove之后会返回的是initialValue方法的返回值。
可以看到调用的是ThreadLocalMap的方法:
想一下,执行结果是什么?是0吗?答案是1。这和开头的例子怎么不一样啊?难道用的是假的ThreadLocal?说好的不同线程之间相互不影响呢?
我们看这里和开头的有什么不同,开头的例子主线程和子线程set的对象是不同的,而这里set的却是同一个对象。
也就是说,threadlocal设置不同的对象线程之间相互不影响,如果是同一个对象,那么仍然还是会相互影响。线程副本的意思并不是说set同一个对象,能够把该对象复制一份。实质上仍然是同一个对象。
//获取值 public T get() { } //设置值 public void set(T value) { } //移除 public void remove() { } //初始化值 protected T initialValue() { }
ThreadLocal的使用
我们看看ThreadLocal怎么用:package com.rancho945.concurrent; public class Test { public static void main(String[] args) { final ThreadLocal<String> local = new ThreadLocal<String>(); //注意这里是在主线程里设置的 local.set("hello world"); new Thread(new Runnable() { @Override public void run() { //我们看看能不能在另外的线程获取到主线程设置的local值 System.out.println("1."+Thread.currentThread().getName()+"---"+local.get()); //然后新线程设置值 local.set("hello ThreadLocal"); //获取并输出输出 System.out.println("2."+Thread.currentThread().getName()+"---"+local.get()); //移除 local.remove(); //输出 System.out.println("3."+Thread.currentThread().getName()+"---"+local.get()); } }).start(); //保证子线程执行完成 try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } //主线程获取并输出 System.out.println("4."+Thread.currentThread().getName()+"---"+local.get()); } }
执行结果
1.Thread-0---null 2.Thread-0---hello ThreadLocal 3.Thread-0---null 4.main---hello world
可以看到,虽然local实例是两个线程共享的,但是他们之间设值以及取值是不会相互影响的,这就是本地线程变量。
我们在看看initialValue是怎么用的,把上面的例子中的
final ThreadLocal<String> local = new ThreadLocal<String>();
改为
final ThreadLocal<String> local = new ThreadLocal<String>(){ @Override protected String initialValue() { // TODO Auto-generated method stub return "hehe"; } };
那么执行结果为:
1.Thread-0---hehe 2.Thread-0---hello ThreadLocal 3.Thread-0---hehe 4.main---hello world
可以看到,如果重写了initialValue,那么当线程没有设置值或者已经移除了,那么会将initialValue返回值作为默认值。
源码分析
ThreadLocal类里面有个内部类ThreadLocalMap,负责ThreadLocal与线程本地变量之间的关系,Thread类有一个ThreadLocalMap类的成员变量。看看ThreadLocal类的成员变量
//每个Thread都有一个哈希码 private final int threadLocalHashCode = nextHashCode(); //用于获取下一个哈希码 private static AtomicInteger nextHashCode = new AtomicInteger(); //哈希的增长,这个是一个非常神奇的黄金数字,可以使哈希结果非常均匀地分布在2^N的空间里,具体实现可以搜索一下,不多说。 private static final int HASH_INCREMENT = 0x61c88647; //获取哈希码永远都是自增长0x61c88647,一个非常神奇的数字。 private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }
set(T value)方法
看看ThreadLocal类的set方法:public void set(T value) { //获取当前线程 Thread t = Thread.currentThread(); //获取线程的TheadLocalMap ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
在Thread中:
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap为ThreadLocal的一个静态内部类,初始时获取到线程的map为null,也就是线程的threadLocals是延迟初始化的,线程没有用到ThreadLocal功能的时候就不创建对象,避免冗余,充分体现了JDK大神深厚的编码功底。我们先看看createMap的实现:
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
这里是创建当前线程的threadLocals变量,注意这里传进去的第一个参数是this,也就是当前的threadlocal实例对象。看看ThreadLocalMap内部类
/** * The initial capacity -- MUST be a power of two. */ //这里是初始化容量 private static final int INITIAL_CAPACITY = 16; /** * The table, resized as necessary. * table.length MUST always be a power of two. */ //这里是保存键值对的对象数组,Entry负责保存一个键值对,表示threadloacl对象实例和值的关系 private Entry[] table; /** * The number of entries in the table. */ //表示当前的规模的大小 private int size = 0; /** * The next size value at which to resize. */ //这里是需要重新扩充table的时size上限大小 private int threshold; // Default to 0 //这里是真正保存ThreadLocal实例对象和值的地方,用了一个弱引用来引用ThreadLocal实例对象 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<?> firstKey, Object firstValue) { //tabale初始化的,大小为INITIAL_CAPACITY table = new Entry[INITIAL_CAPACITY]; //这个就是根据前面很神奇的0x61c88647有关,这里算出的值可以使多个threadlocal均匀分布在数组中, int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); //在数组i的位置存放threadlocal与线程本地变量的k-v关系对象 table[i] = new Entry(firstKey, firstValue); //设置当前大小 size = 1; setThreshold(INITIAL_CAPACITY); } private void setThreshold(int len) { //这里设置重新调整size为len的三分之二 //比如初始化的时候为16,那么16*2/3=10就要对table扩容 threshold = len * 2 / 3; }
这里的主要工作就是给当前线程创建ThreadLocalMap实例,把当前threadlocal对象和需要放进去的值关系储存在ThreadLocalMap实例的table中。如果说一个线程用了三个threadlocal对象,比如:
final ThreadLocal<String> local1 = new ThreadLocal<String>(); final ThreadLocal<String> local2 = new ThreadLocal<String>(); final ThreadLocal<String> local3 = new ThreadLocal<String>(); new Thread(new Runnable() { @Override public void run() { local1.set("ThreadLocal1"); local2.set("ThreadLocal2"); local3.set("ThreadLocal3"); } }).start();
那么local1和”ThreadLocal1”作为一个entry放在线程threadLocals(ThreadlocalMap实例)变量的table中,local2和”ThreadLocal2”作为一个entry也放该table中,local2和”ThreadLocal2”作为一个entry也放在table中。当然后面两个entry不是在create方法中放进去的,以为第一次create后,getMap方法就已经不在返回null,而是执行map.set()
public void set(T value) { //获取当前线程 Thread t = Thread.currentThread(); //获取线程的TheadLocalMap ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
看看map.set():
/** * Set the value associated with key. * * @param key the thread local object * @param value the value to be set */ private void set(ThreadLocal<?> key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); //这里从table的i处开始查找,直到找到entry为空 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); //如果找到的key和要设置的key相同,则证明是同一个threadlocal,把它的值替换一下,返回 if (k == key) { e.value = value; return; } //如果找到的key为null,证明已经被回收,那么久把该清除并用当前的k-v替换,然后返回,具体替换过程不再仔细分析 if (k == null) { replaceStaleEntry(key, value, i); return; } } //如果没有在table中找到,那么在i的位置新增一个节点。 tab[i] = new Entry(key, value); int sz = ++size; //这里也是清除一些被回收的Threadlocal对象在table中的位置,并且判断是否应该扩容。 if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); }
get()方法
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { //使用当前threadlocal获取线程ThreadLocalMap中的entry ThreadLocalMap.Entry e = map.getEntry(this); //如果找到则直接返回 if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //没有找到 return setInitialValue(); } private T setInitialValue() { //默认为空 T value = initialValue(); //下面的分析和前面set方法一样 Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } //如果新建ThreadLocal的时候没有重写,则默认返回空 protected T initialValue() { return null; }
这也就解释了前面为什么没有set之前或者remove之后会返回的是initialValue方法的返回值。
remove()方法
ThreadLocal中的remove方法:public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
可以看到调用的是ThreadLocalMap的方法:
/** * Remove the entry for key. */ private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); //找到entry for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { //如果找到了,则清除 if (e.get() == key) { //这里清除entry的对threadlocal对象的引用 e.clear(); //这里清除的是table中i位置的entry expungeStaleEntry(i); return; } } }
思考
看看下面的代码执行会输出什么package com.rancho945.concurrent; public class Counter { public int count = 0 ; public void increment(){ count++; } }
package com.rancho945.concurrent; public class Test { public static void main(String[] args) { final ThreadLocal<Counter> local = new ThreadLocal<Counter>(); final Counter counter = new Counter(); //注意这里是在主线程里设置的,默认count值为0 local.set(counter); //在主线程中自增 counter.increment(); new Thread(new Runnable() { @Override public void run() { local.set(counter); //子线程从同一个local中获取count System.out.println(local.get().count); } }).start(); } }
想一下,执行结果是什么?是0吗?答案是1。这和开头的例子怎么不一样啊?难道用的是假的ThreadLocal?说好的不同线程之间相互不影响呢?
我们看这里和开头的有什么不同,开头的例子主线程和子线程set的对象是不同的,而这里set的却是同一个对象。
也就是说,threadlocal设置不同的对象线程之间相互不影响,如果是同一个对象,那么仍然还是会相互影响。线程副本的意思并不是说set同一个对象,能够把该对象复制一份。实质上仍然是同一个对象。
相关文章推荐
- Spring源码分析【6】-ThreadLocal的使用和源码分析
- Muduo网络库源码分析(三)线程间使用eventfd通信和EventLoop::runInLoop系列函数
- Android进阶——多线程系列之异步任务AsyncTask的使用与源码分析
- 分析balde源码,查看Web工程处理Ioc注入的背后的过程,ThreadLocal 使用保存线程所有的request and respond,
- java基础解析系列(十)---ArrayList和LinkedList源码及使用分析
- libumem使用和源码分析系列文章(二)
- ThreadLocal源码分析与使用场景
- ThreadLocal使用及源码分析
- ThreadLocal使用和源码分析
- libumem使用和源码分析系列文章(一)
- lesson1:threadlocal的使用demo及源码分析
- ThreadLocal基本使用和源码分析
- .NET / Rotor源码分析5 - 开始使用WinDbg+SOS调试,sscoree.dll,加载SOS并设置JIT断点
- 第二人生的源码分析(六十六)使用Expat XML解析器的例子
- 第二人生的源码分析(五十八)使用FreeType字体
- 第二人生的源码分析(六十七)LLXMLNode使用Expat库打开文件
- 第二人生的源码分析(六十六)使用Expat XML解析器的例子
- .NET / Rotor源码分析5 - 开始使用WinDbg+SOS调试,sscoree.dll,加载SOS并设置JIT断点
- 第二人生的源码分析(六十八)LLXMLNode使用Expat库分析XML文件
- 第二人生的源码分析(四十一)使用Apache运行库线程