您的位置:首页 > 编程语言 > Java开发

Java中线程局部变量ThreadLocal使用教程及源码分析

2016-05-23 21:19 477 查看
在Java多线程编程中有时候会遇见线程本地局部变量ThreadLocal这个类,下面就来讲讲ThreadLocal的使用及源码分析。

ThreadLocal 是Thread Local Varial(线程局部变量)的意思,每个线程在使用线程局部变量的时候都会为使用这个线程局部变量的线程提供一个线程局部变量的副本,使得每个线程都可以完全独立地操作这个线程局部变量,而不会与其他线程发生冲突,从线程的角度来看,每个线程都好像独立地拥有了这个线程局部变量。这样,看似每个线程都在并发访问同一个资源(线程局部变量ThreadLocal),但每个线程都是各自独立地操作这个线程变量,而与其它线程没有任何关系,线程之间对线程局部变量的操作都没有任何干扰。而多线程同步机制是为了保证在任何一个时刻只有一个线程可以对共享资源进行修改。即ThreadLocal是为了隔离对资源的共享,而同步机制是为了同步多个线程对同一个共享资源的访问,是多个线程之间的通信方式。

ThreadLocal主要有以下几个方法:

T ThreadLocal.get():返回此线程局部变量中当前线程的副本值。

ThreadLocal.set(T value):给线程局部变量中当前线程的副本中设置相应的值。

ThreadLocal.remove():删除当前线程局部变量中的值。

下面就给一个ThreadLocal的测试例程,可以帮助大家理解。

<span style="font-size:18px;"><span style="font-size:18px;">import java.util.HashMap;

/**
* 文件名称:ThreadLocal测试例程ThreadLocalTest
* 时间:2016 5-23 17:35
* 说明:1、同步机制是为了同步多个线程对相同资源的并发访问,是多个线程之间进行通信的有效方式;而ThreadLocal是为了隔离多个
* 线程的数据共享,隔离多个线程之间的共享冲突。
* */
public class ThreadLocalTest {
private static HashMap<Integer,Integer> mMap=new HashMap<Integer,Integer>();

private static ThreadLocal<Integer> mThreadLocal=new ThreadLocal<Integer>();

static class  FirstThread extends Thread
{
int n;
int i=0;

public FirstThread() {
super();
// TODO Auto-generated constructor stub
}

public FirstThread(String name) {
super(name);
// TODO Auto-generated constructor stub
}

@Override
public void run() {
if(mMap.get(0)!=null){
n=mMap.get(0);
}
for(i=0;i<5;i++){
n++;
}
mMap.put(0,n);
System.out.println(currentThread().getName()+",value="+mMap.get(0));
}
}

static class SecondThread extends Thread
{
int n=0;
int i=0;

public SecondThread() {
super();
// TODO Auto-generated constructor stub
}

public SecondThread(String name) {
// TODO Auto-generated constructor stub
super(name);
}

@Override
public void run() {
if(mThreadLocal.get()!=null){
n=mThreadLocal.get();
}
for(i=0;i<5;i++){
n++;
}
mThreadLocal.set(n);
System.out.println(currentThread().getName()+",value="+mThreadLocal.get());
}
}

/**
* @param args
*/
public static void main(String[] args) {
int i=0;
for(i=0;i<5;i++){
new FirstThread("FirstThread"+i).start();
}

for(i=0;i<5;i++){
new SecondThread("SecondThread"+i).start();
}
}

}
</span></span>


运行结果如下:



可以发现FirstThread是对同一个共享资源mMap访问,多个线程对mMap.get(0)中的值进行修改导致每个线程运行后读取到mMap.get(0)各不一样。而SecondThread由于使用了ThreadLocal这样,每个线程中都有一个ThreadLocal副本,就像每个线程各自拥有一个ThreadLocal变量一样,这样SecondThread运行后读取到的数据都是一样的。

测试例程

下面来分析一下ThreadLocal源码,我们以Android4.3中的ThreadLocal源码来分析。首先来看下ThreadLocal的结构:



观察发现ThreadLocal里面主要get、set、remove等常见方法及一些属性变量,并含有一个静态的内部类Values,

<span style="font-size:18px;">/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0 *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package java.lang;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.concurrent.atomic.AtomicInteger;

/**
* Implements a thread-local storage, that is, a variable for which each thread
* has its own value. All threads share the same {@code ThreadLocal} object,
* but each sees a different value when accessing it, and changes made by one
* thread do not affect the other threads. The implementation supports
* {@code null} values.
*
* @see java.lang.Thread
* @author Bob Lee
* ThreadLocal<T>是一个泛型类,表示线程局部变量,泛型T是要存储数据的类型,将TreadLocal类型为T的数据
* 按照key-value键值对的形式存入到Values对象中
*
*/
public class ThreadLocal<T> {

/* Thanks to Josh Bloch and Doug Lea for code reviews and impl advice. */

/**
* Creates a new thread-local variable.
*/
public ThreadLocal() {}

/**
* Returns the value of this variable for the current thread. If an entry
* doesn't yet exist for this variable on this thread, this method will
* create an entry, populating the value with the result of
* {@link #initialValue()}.
*
* @return the current value of the variable for the calling thread.
*/
@SuppressWarnings("unchecked")
public T get() {
// Optimized for the fast path.
/*获取当前线程*/
Thread currentThread = Thread.currentThread();
/*获取当前线程的Values实例对象*/
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
/*获取key的索引值*/
int index = hash & values.mask;
/*如果键值的key的索引为index,则所对应到的value索引为index+1.*/
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
/*如果当前Value实例为空,则创建一个Value实例*/
values = initializeValues(currentThread);
}

return (T) values.getAfterMiss(this);
}

/**
* Provides the initial value of this variable for the current thread.
* The default implementation returns {@code null}.
*
* @return the initial value of the variable.返回当前线程线程局部变量ThreadLocal的初始值
*/
protected T initialValue() {
return null;
}

/**
* Sets the value of this variable for the current thread. If set to
* {@code null}, the value will be set to null and the underlying entry will
* still be present.
*
* @param value the new value of the variable for the caller thread.
*/
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}

/**
* Removes the entry for this variable in the current thread. If this call
* is followed by a {@link #get()} before a {@link #set},
* {@code #get()} will call {@link #initialValue()} and create a new
* entry with the resulting value.
*
* @since 1.5
*/
public void remove() {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
values.remove(this);
}
}

/**
* Creates Values instance for this thread and variable type.
* 给当前线程创建要给Values实例对象
*/
Values initializeValues(Thread current) {
return current.localValues = new Values();
}

/**
* Gets Values instance for this thread and variable type.
* 获取当前线程的的Values实例对象
*/
Values values(Thread current) {
return current.localValues;
}

/** Weak reference to this thread local instance. */
private final Reference<ThreadLocal<T>> reference
= new WeakReference<ThreadLocal<T>>(this);

/** Hash counter. */
private static AtomicInteger hashCounter = new AtomicInteger(0);

/**
* Internal hash. We deliberately don't bother with #hashCode().
* Hashes must be even. This ensures that the result of
* (hash & (table.length - 1)) points to a key and not a value.
*
* We increment by Doug Lea's Magic Number(TM) (*2 since keys are in
* every other bucket) to help prevent clustering.
*/
private final int hash = hashCounter.getAndAdd(0x61c88647 * 2);

/**
* Per-thread map of ThreadLocal instances to values.
*/
static class Values {

/**
* Size must always be a power of 2.
* 默认的初始化容量.
*/
private static final int INITIAL_SIZE = 16;

/**
* Placeholder for deleted entries.
* 表示被删除的实体标记对象,移除变量时它是把对应的key的位置赋值为TOMBSTONE
*/
private static final Object TOMBSTONE = new Object();

/**
* Map entries. Contains alternating keys (ThreadLocal) and values.
* The length is always a power of 2.
* Table数组,保存变量的地方,长度必须是2的n次方的值.
*/
private Object[] table;

/** Used to turn hashes into indices.
* 计算下标的掩码,它的值是table的长度-1
* */
private int mask;

/** Number of live entries.
* 存放进来的实体的数量
* */
private int size;

/** Number of tombstones.
* 表示被删除的实体的数量
*  */
private int tombstones;

/** Maximum number of live entries and tombstones.
* 阈值,最大负载因子,用来判断是否需要进行rehash
* */
private int maximumLoad;

/** Points to the next cell to clean up.
* 表示下一个要进行清理的位置点
* */
private int clean;

/**
* Constructs a new, empty instance.
*/
Values() {
initializeTable(INITIAL_SIZE);
this.size = 0;
this.tombstones = 0;
}

/**
* Used for InheritableThreadLocals.
*/
Values(Values fromParent) {
this.table = fromParent.table.clone();
this.mask = fromParent.mask;
this.size = fromParent.size;
this.tombstones = fromParent.tombstones;
this.maximumLoad = fromParent.maximumLoad;
this.clean = fromParent.clean;
inheritValues(fromParent);
}

/**
* Inherits values from a parent thread.
*/
@SuppressWarnings({"unchecked"})
private void inheritValues(Values fromParent) {
// Transfer values from parent to child thread.
Object[] table = this.table;
for (int i = table.length - 2; i >= 0; i -= 2) {
Object k = table[i];

if (k == null || k == TOMBSTONE) {
// Skip this entry.
continue;
}

// The table can only contain null, tombstones and references.
Reference<InheritableThreadLocal<?>> reference
= (Reference<InheritableThreadLocal<?>>) k;
// Raw type enables us to pass in an Object below.
InheritableThreadLocal key = reference.get();
if (key != null) {
// Replace value with filtered value.
// We should just let exceptions bubble out and tank
// the thread creation
table[i + 1] = key.childValue(fromParent.table[i + 1]);
} else {
// The key was reclaimed.
table[i] = TOMBSTONE;
table[i + 1] = null;
fromParent.table[i] = TOMBSTONE;
fromParent.table[i + 1] = null;

tombstones++;
fromParent.tombstones++;

size--;
fromParent.size--;
}
}
}

/**
* Creates a new, empty table with the given capacity.
* 根据给定的容量创建要给Table
*/
private void initializeTable(int capacity) {
/*通过capacity创建table容器*/
this.table = new Object[capacity * 2];
/*下标的掩码*/
this.mask = table.length - 1;
/*下一个要进行清理的位置点初始化为0*/
this.clean = 0;
/*最大负载因子*/
this.maximumLoad = capacity * 2 / 3; // 2/3
}

/**
* Cleans up after garbage-collected thread locals.
* 在ThreadLocal对象被垃圾回收之后进行清理
*/
private void cleanUp() {
if (rehash()) {
// If we rehashed, we needn't clean up (clean up happens as
// a side effect).
return;
}

if (size == 0) {
// No live entries == nothing to clean.
return;
}

// Clean log(table.length) entries picking up where we left off
// last time.
int index = clean;
Object[] table = this.table;
for (int counter = table.length; counter > 0; counter >>= 1,
index = next(index)) {
Object k = table[index];
/*如果已经删除,则删除下一个*/
if (k == TOMBSTONE || k == null) {
continue; // on to next entry
}

// The table can only contain null, tombstones and references.
@SuppressWarnings("unchecked")
Reference<ThreadLocal<?>> reference
= (Reference<ThreadLocal<?>>) k;/*将从Table数组中获取到的对象进行强转*/

/*如果ThreadLocal对象已经不存在,则释放value数据*/
if (reference.get() == null) {
// This thread local was reclaimed by the garbage collector.
table[index] = TOMBSTONE;
table[index + 1] = null;
tombstones++;
size--;
}
}

// Point cursor to next index.
clean = index;
}

/**
* Rehashes the table, expanding or contracting it as necessary.
* Gets rid of tombstones. Returns true if a rehash occurred.
* We must rehash every time we fill a null slot; we depend on the
* presence of null slots to end searches (otherwise, we'll infinitely
* loop).
*/
private boolean rehash() {
if (tombstones + size < maximumLoad) {
return false;
}

int capacity = table.length >> 1;

// Default to the same capacity. This will create a table of the
// same size and move over the live entries, analogous to a
// garbage collection. This should only happen if you churn a
// bunch of thread local garbage (removing and reinserting
// the same thread locals over and over will overwrite tombstones
// and not fill up the table).
int newCapacity = capacity;

if (size > (capacity >> 1)) {
// More than 1/2 filled w/ live entries.
// Double size.
newCapacity = capacity * 2;
}

Object[] oldTable = this.table;

// Allocate new table.
initializeTable(newCapacity);

// We won't have any tombstones after this.
this.tombstones = 0;

// If we have no live entries, we can quit here.
if (size == 0) {
return true;
}

// Move over entries.
for (int i = oldTable.length - 2; i >= 0; i -= 2) {
Object k = oldTable[i];
if (k == null || k == TOMBSTONE) {
// Skip this entry.
continue;
}

// The table can only contain null, tombstones and references.
@SuppressWarnings("unchecked")
Reference<ThreadLocal<?>> reference
= (Reference<ThreadLocal<?>>) k;
ThreadLocal<?> key = reference.get();
if (key != null) {
// Entry is still live. Move it over.
add(key, oldTable[i + 1]);
} else {
// The key was reclaimed.
size--;
}
}

return true;
}

/**
* Adds an entry during rehashing. Compared to put(), this method
* doesn't have to clean up, check for existing entries, account for
* tombstones, etc.
*/
void add(ThreadLocal<?> key, Object value) {
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == null) {
/*把ThreadLocal对象(key)和对应的value放在连续的位置中*/
table[index] = key.reference;
table[index + 1] = value;
return;
}
}
}

/**
* Sets entry for given ThreadLocal to given value, creating an
* entry if necessary.
*/
void put(ThreadLocal<?> key, Object value) {
cleanUp();/*清理废弃的元素*/

// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;

for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];

/*如果TheadLocal对象是存在,存入value对象*/
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}

if (k == null) {
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
}

// Go back and replace first tombstone.
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
}

// Remember first tombstone.
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}

/**
* Gets value for given ThreadLocal after not finding it in the first
* slot.
*/
Object getAfterMiss(ThreadLocal<?> key) {
Object[] table = this.table;
int index = key.hash & mask;

// If the first slot is empty, the search is over.
if (table[index] == null) {
Object value = key.initialValue();

// If the table is still the same and the slot is still empty...
if (this.table == table && table[index] == null) {
table[index] = key.reference;
table[index + 1] = value;
size++;

cleanUp();
return value;
}

// The table changed during initialValue().
put(key, value);
return value;
}

// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;

// Continue search.
for (index = next(index);; index = next(index)) {
Object reference = table[index];
if (reference == key.reference) {
return table[index + 1];
}

// If no entry was found...
if (reference == null) {
Object value = key.initialValue();

// If the table is still the same...
if (this.table == table) {
// If we passed a tombstone and that slot still
// contains a tombstone...
if (firstTombstone > -1
&& table[firstTombstone] == TOMBSTONE) {
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;

// No need to clean up here. We aren't filling
// in a null slot.
return value;
}

// If this slot is still empty...
if (table[index] == null) {
table[index] = key.reference;
table[index + 1] = value;
size++;

cleanUp();
return value;
}
}

// The table changed during initialValue().
put(key, value);
return value;
}

if (firstTombstone == -1 && reference == TOMBSTONE) {
// Keep track of this tombstone so we can overwrite it.
firstTombstone = index;
}
}
}

/**
* Removes entry for the given ThreadLocal.
*/
void remove(ThreadLocal<?> key) {
cleanUp();

for (int index = key.hash & mask;; index = next(index)) {
Object reference = table[index];

if (reference == key.reference) {
// Success!
table[index] = TOMBSTONE;
table[index + 1] = null;
tombstones++;
size--;
return;
}

if (reference == null) {
// No entry found.
return;
}
}
}

/**
* Gets the next index. If we're at the end of the table, we wrap back
* around to 0.
*/
private int next(int index) {
return (index + 2) & mask;
}
}
}
</span>
首先看下ThreadLocal类里面几个属性,

<span style="font-size:18px;">    /** Weak reference to this thread local instance. */
private final Reference<ThreadLocal<T>> reference
= new WeakReference<ThreadLocal<T>>(this);
</span>


使用弱引用来存储ThreadLocal对象本身,对于只使用弱引用的对象而言,当系统运行垃圾回收机制时不管系统内存是否足够,总会回收该对象所占内存

<span style="font-size:18px;"><pre name="code" class="java">    /** Hash counter. */
private static AtomicInteger hashCounter = new AtomicInteger(0);

/**
* Internal hash. We deliberately don't bother with #hashCode().
* Hashes must be even. This ensures that the result of
* (hash & (table.length - 1)) points to a key and not a value.
*
* We increment by Doug Lea's Magic Number(TM) (*2 since keys are in
* every other bucket) to help prevent clustering.
*/
private final int hash = hashCounter.getAndAdd(0x61c88647 * 2);</span>



hashCounter对象是可以进行加、减等安全操作的整数。getAndSet(int newValue)取当前的值,并设置新的值;而0x61c88647 * 2作用是:在Value存在的数据主要存储数组table上,而table被设计为下标为0,2,4…2n的位置存放key,而1,3,5…(2n +1 )的位置存放value,`0x61c88647 * 2保证了其二进制中最低位为0,也就是在计算key的下标时,一定是偶数位。

下面来看下ThreadLocal.get方法的内部实现:

<span style="font-size:18px;">    /**
* Returns the value of this variable for the current thread. If an entry
* doesn't yet exist for this variable on this thread, this method will
* create an entry, populating the value with the result of
* {@link #initialValue()}.
*
* @return the current value of the variable for the calling thread.
*/
@SuppressWarnings("unchecked")
public T get() {
// Optimized for the fast path.
/*获取当前线程*/
Thread currentThread = Thread.currentThread();
/*获取当前线程的Values实例对象*/
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
/*获取key的索引值*/
int index = hash & values.mask;
/*如果键值的key的索引为index,则所对应到的value索引为index+1.*/
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
/*如果当前Value实例为空,则创建一个Value实例*/
values = initializeValues(currentThread);
}

return (T) values.getAfterMiss(this);
}</span>
在ThreadLocal.get方法中首先获取ThreadLocal线程局部变量所对应的当前线程,即得到ThreadLocal线程局部变量所在线程,然后调用ThreadLocal.values(Thread current)方法得到当前线程对应的ThreadLocal对象,由前面的讲解可知道ThreadLocal变量是与每个线程对应的,进入ThreadLocal.values(Thread current)可以发现如下代码:

<span style="font-size:18px;">    /**
* Gets Values instance for this thread and variable type.
* 获取当前线程的的Values实例对象
*/
Values values(Thread current) {
return current.localValues;
}</span>
而current.localValues是Thread类中的一个属性,进一步追踪在Thread类中可以发现

<span style="font-size:18px;">    /**
* Normal thread local values.
*/
ThreadLocal.Values localValues;</span>


因此这样每个线程与一个ThreadLocal对象一一对象,当通过ThreadLocal.values(Thread current)方法得到的values对象时如果为空调用ThreadLocal.initializeValues(Thread current)方法来初始化一个values对象,如果获取到的values对象不为空则取出类型为泛型T的数据。如果获取到的Values类型的values对象不为空的话,拿到values.table对象数组,然后按照一定取出values.table数组中的值,注意在Values类型的对象中含有一个table对象数组,该对象数组中0、2、4……等偶数下标位置存储key键,1、3、5……等奇数下标位置存储value值,通过这种方式来存储key-value键值对,关于ThreadLocal中静态内部类Values的内部实现后面会有一定的分析,Values类型数据就是用来存储key-value的键值对。

既然看完ThreadLocal.get()方法中的内部实现,接下来就看看ThreadLocal.set(T value)内部实现,

/**
* Sets the value of this variable for the current thread. If set to
* {@code null}, the value will be set to null and the underlying entry will
* still be present.
*
* @param value the new value of the variable for the caller thread.
*/
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}


和get方法类似,set方法首先拿到当前线程,然后再拿到当前线程的Values类型对象values,如果values为空则调用ThreadLocal.initializeValues(Thread current)对values进行初始化,最后再向values中存入数据。

下面来分析一下Values内部类的实现,Values几个重要的属性如下:

/**
* Size must always be a power of 2.
* 默认的初始化容量.
*/
private static final int INITIAL_SIZE = 16;

/**
* Placeholder for deleted entries.
* 表示被删除的实体标记对象,移除变量时它是把对应的key的位置赋值为TOMBSTONE
*/
private static final Object TOMBSTONE = new Object();

/**
* Map entries. Contains alternating keys (ThreadLocal) and values.
* The length is always a power of 2.
* Table数组,保存变量的地方,长度必须是2的n次方的值.
*/
private Object[] table;

/** Used to turn hashes into indices.
* 计算下标的掩码,它的值是table的长度-1
* */
private int mask;

/** Number of live entries.
* 存放进来的实体的数量
* */
private int size;

/** Number of tombstones.
* 表示被删除的实体的数量
*  */
private int tombstones;

/** Maximum number of live entries and tombstones.
* 阈值,最大负载因子,用来判断是否需要进行rehash
* */
private int maximumLoad;

/** Points to the next cell to clean up.
* 表示下一个要进行清理的位置点
* */
private int clean;


Values的构造函数如下:

/**
* Constructs a new, empty instance.
*/
Values() {
initializeTable(INITIAL_SIZE);
this.size = 0;
this.tombstones = 0;
}

/**
* Used for InheritableThreadLocals.
*/
Values(Values fromParent) {
this.table = fromParent.table.clone();
this.mask = fromParent.mask;
this.size = fromParent.size;
this.tombstones = fromParent.tombstones;
this.maximumLoad = fromParent.maximumLoad;
this.clean = fromParent.clean;
inheritValues(fromParent);
}
对于Values中的方法我们以ThreadLocal.Values.put(ThreadLocal<?> key, Object value)方法为例进行分析,

/**
* Sets entry for given ThreadLocal to given value, creating an
* entry if necessary.
*/
void put(ThreadLocal<?> key, Object value) {
cleanUp();/*清理废弃的元素*/

// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;

for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];

/*如果TheadLocal对象是存在,存入value对象*/
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}

if (k == null) {
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
}

// Go back and replace first tombstone.
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
}

// Remember first tombstone.
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}


可以发现通过Values对象存储key-value键值对是放在一个一维的Object[]数组中,偶数下标位存储key,奇数下标位存储value。ThreadLocal.Values.getAfterMiss(ThreadLocal<?> key)等方法亦是如此。

ThreadLocal源码(版本:Android4.3,,含注释)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: